From fce02be1c71383c62be0ed97b20c55f69921cdd1 Mon Sep 17 00:00:00 2001 From: oojacoboo Date: Tue, 16 Jul 2024 22:14:00 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20thecodin?= =?UTF-8?q?gmachine/graphqlite@069d62a69eeeb97f8559d7623ab137774863e7e0=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 404.html | 8 ++++---- .../js/{01fe3043.3fad64b7.js => 01fe3043.57dca576.js} | 2 +- .../js/{029c6d75.43d4c2f1.js => 029c6d75.6e76e079.js} | 2 +- .../js/{02c5a8b1.35dbaf12.js => 02c5a8b1.ca45c617.js} | 2 +- .../js/{0343976d.d295fc38.js => 0343976d.dc3209ed.js} | 2 +- .../js/{0370fbfb.5a5b55ad.js => 0370fbfb.b873a3e9.js} | 2 +- .../js/{03abab96.4f809df3.js => 03abab96.1c428084.js} | 2 +- .../js/{03c886f6.b5776fa5.js => 03c886f6.b2d0817c.js} | 2 +- .../js/{05e8cfc0.14924a11.js => 05e8cfc0.7b22ffae.js} | 2 +- .../js/{05fed6b1.5b1f7b07.js => 05fed6b1.50b5309d.js} | 2 +- .../js/{06c02cc7.5a703743.js => 06c02cc7.8457a813.js} | 2 +- .../js/{07623f9a.e42afba5.js => 07623f9a.3b28ef48.js} | 2 +- .../js/{07666c14.6ed70fed.js => 07666c14.64d0038d.js} | 2 +- .../js/{077a13b8.95292adb.js => 077a13b8.3f86c654.js} | 2 +- .../js/{079b0d3e.b634668e.js => 079b0d3e.6516370a.js} | 2 +- .../js/{07c49ebd.cbc22070.js => 07c49ebd.f683e66a.js} | 2 +- .../js/{085c135f.dccfc8aa.js => 085c135f.a8d57bf6.js} | 2 +- .../js/{08fe23a4.fd364b45.js => 08fe23a4.be9b2800.js} | 2 +- .../js/{0a57d896.019ac7d9.js => 0a57d896.e912e3ff.js} | 2 +- .../js/{0cb7e976.b7d4f44b.js => 0cb7e976.2f6d64b5.js} | 2 +- .../js/{0d7bb119.295445f4.js => 0d7bb119.c337f7c6.js} | 2 +- .../js/{0db009bb.2dc8137c.js => 0db009bb.5e17f885.js} | 2 +- .../js/{0db959c8.0e2c30f2.js => 0db959c8.fb424422.js} | 2 +- .../js/{0df2ba32.2e7b3274.js => 0df2ba32.64b69ad9.js} | 2 +- .../js/{0e5befdb.eb114fa3.js => 0e5befdb.28a1500f.js} | 2 +- .../js/{0ef60658.e8767085.js => 0ef60658.bf770bdb.js} | 2 +- .../js/{0fd21208.36dd45ca.js => 0fd21208.a724c6b4.js} | 2 +- .../js/{102de343.a2b42f70.js => 102de343.1b48167b.js} | 2 +- .../js/{107b7a36.0c653dcc.js => 107b7a36.f7b0c4e9.js} | 2 +- .../js/{107d11ee.272fa44b.js => 107d11ee.e951c451.js} | 2 +- .../js/{12d3ef9e.563d2fda.js => 12d3ef9e.e80151c3.js} | 2 +- .../js/{136c1ee9.c16d163a.js => 136c1ee9.5b3b5c84.js} | 2 +- .../js/{13b4aeb1.56d9e47f.js => 13b4aeb1.d2956e3d.js} | 2 +- .../js/{1428bdad.9d14ab8c.js => 1428bdad.014df04c.js} | 2 +- .../js/{143f7888.154e6dae.js => 143f7888.0030da3c.js} | 2 +- .../js/{15a79915.fee0e891.js => 15a79915.9786fff1.js} | 2 +- .../js/{15b5a907.83328341.js => 15b5a907.5de5d297.js} | 2 +- .../js/{16017aa6.d535d4c6.js => 16017aa6.25f7826c.js} | 2 +- .../js/{16565e6a.23cc4bae.js => 16565e6a.6e039561.js} | 2 +- .../js/{17518879.44a8bc9b.js => 17518879.07b10d98.js} | 2 +- assets/js/{1774.8c292c27.js => 1774.9b58a5d2.js} | 2 +- .../js/{17cca601.ca1dfe7d.js => 17cca601.e153a41a.js} | 2 +- .../js/{18100524.c086b122.js => 18100524.e30015f9.js} | 2 +- .../js/{1891fd2b.646738f3.js => 1891fd2b.b4b19628.js} | 2 +- .../js/{18d6c9c9.74a8728d.js => 18d6c9c9.8e7d66c1.js} | 2 +- assets/js/1a4e3797.0725399b.js | 2 -- assets/js/1a4e3797.0725399b.js.LICENSE.txt | 1 - assets/js/1a4e3797.491d505b.js | 2 ++ assets/js/1a4e3797.491d505b.js.LICENSE.txt | 1 + .../js/{1aa05129.30e43841.js => 1aa05129.8d91a7ea.js} | 2 +- .../js/{1af245cd.0b878aaa.js => 1af245cd.ceaf76e0.js} | 2 +- .../js/{1b1927f4.70837fdd.js => 1b1927f4.999e377f.js} | 2 +- .../js/{1ba75d10.077c9e1f.js => 1ba75d10.f25708bf.js} | 2 +- .../js/{1be78505.9bd92206.js => 1be78505.f41f285f.js} | 2 +- .../js/{1ca907c0.8af28e47.js => 1ca907c0.475dd85b.js} | 2 +- .../js/{1d20a4b3.a82a7192.js => 1d20a4b3.3ae04557.js} | 2 +- .../js/{1d703573.f3aa0b11.js => 1d703573.c56e80a3.js} | 2 +- .../js/{1e2c5f46.3a7a0bf9.js => 1e2c5f46.2622c239.js} | 2 +- .../js/{1e6ec01e.8ee3df72.js => 1e6ec01e.b01c0803.js} | 2 +- .../js/{1e7fe27e.6683c9a6.js => 1e7fe27e.1eb07132.js} | 2 +- .../js/{1ea13486.7fc84ccf.js => 1ea13486.10865491.js} | 2 +- .../js/{1edb88e5.3fcd7961.js => 1edb88e5.24a4d76c.js} | 2 +- .../js/{1f5af0f2.db54f2d9.js => 1f5af0f2.918dcc76.js} | 2 +- .../js/{1f5e9707.9f12226d.js => 1f5e9707.816a8926.js} | 2 +- .../js/{2014e4e3.cdee6c53.js => 2014e4e3.d25fa2df.js} | 2 +- .../js/{20540af3.a43cc815.js => 20540af3.26b7ee01.js} | 2 +- .../js/{21637dff.b4c6abe5.js => 21637dff.327925d3.js} | 2 +- .../js/{21a7a3b0.b7e5fa05.js => 21a7a3b0.f2e2fc55.js} | 2 +- .../js/{21cde469.af4cb392.js => 21cde469.97a411b3.js} | 2 +- .../js/{22e1e32f.891fb0d4.js => 22e1e32f.fd1edf1e.js} | 2 +- .../js/{232afa3a.e9a3d7db.js => 232afa3a.77394b96.js} | 2 +- .../js/{2355609d.3e569e61.js => 2355609d.ff4b6802.js} | 2 +- .../js/{23794275.2ada9f7e.js => 23794275.9a8d8d95.js} | 2 +- .../js/{23a8ac29.b99ca107.js => 23a8ac29.8abcea32.js} | 2 +- .../js/{23f642f2.30bbac0d.js => 23f642f2.3e877c32.js} | 2 +- .../js/{242d99d9.589df066.js => 242d99d9.86bda269.js} | 2 +- .../js/{24ac61c7.cd8e555e.js => 24ac61c7.52eb9adb.js} | 2 +- .../js/{24aca886.954f6b8f.js => 24aca886.82ecee1c.js} | 2 +- .../js/{25d4129e.4553cb4f.js => 25d4129e.6d8bd7b7.js} | 2 +- .../js/{263ebc7a.2e214490.js => 263ebc7a.c6dc1aef.js} | 2 +- .../js/{26662da3.26149760.js => 26662da3.da5a7b07.js} | 2 +- .../js/{26a27afb.fd1bb7ed.js => 26a27afb.acfabf90.js} | 2 +- .../js/{27258a7d.7cbb6f26.js => 27258a7d.5e5152b8.js} | 2 +- .../js/{27b414e3.e46c4fea.js => 27b414e3.549f2a3c.js} | 2 +- .../js/{28c12eaf.e0ba58bd.js => 28c12eaf.77b5e230.js} | 2 +- .../js/{2917b31e.d68b38c9.js => 2917b31e.e312ae9c.js} | 2 +- .../js/{29a6c1ba.6de15e74.js => 29a6c1ba.80f0a1f0.js} | 2 +- .../js/{29cf2ad6.20b4bd04.js => 29cf2ad6.30734bf9.js} | 2 +- .../js/{2b26025e.fc3ba436.js => 2b26025e.e6842bcd.js} | 2 +- .../js/{2bbfc5d5.21599b38.js => 2bbfc5d5.5e6bcac0.js} | 2 +- .../js/{2d02c83c.ff4ec7ba.js => 2d02c83c.abba1f2c.js} | 2 +- .../js/{2d4548df.8a0eaadc.js => 2d4548df.098efc22.js} | 2 +- .../js/{2e25c87f.3d095e48.js => 2e25c87f.a3639132.js} | 2 +- .../js/{2e301473.75e9b1f9.js => 2e301473.0e9607a9.js} | 2 +- .../js/{2ef99682.38a680cf.js => 2ef99682.ef9dafe1.js} | 2 +- .../js/{2f36012a.a4789889.js => 2f36012a.20cd1d0e.js} | 2 +- .../js/{30940d42.1645291e.js => 30940d42.eade2ecb.js} | 2 +- .../js/{31b4e903.05b641cb.js => 31b4e903.6de1dddd.js} | 2 +- .../js/{323a980a.4cafa56c.js => 323a980a.ec878f2d.js} | 2 +- .../js/{332827b4.28485cff.js => 332827b4.a14565af.js} | 2 +- .../js/{346bcb92.3d2a49dd.js => 346bcb92.db7b61ce.js} | 2 +- .../js/{354a9b78.bf15845e.js => 354a9b78.31211d7c.js} | 2 +- .../js/{366cfce3.dba6ab85.js => 366cfce3.cb9991ef.js} | 2 +- .../js/{36ddade1.44eb30cc.js => 36ddade1.67fe7148.js} | 2 +- .../js/{379bfe51.3af3b9e7.js => 379bfe51.971c848c.js} | 2 +- .../js/{380575ae.84b8cf16.js => 380575ae.4091e635.js} | 2 +- .../js/{38317547.182d255d.js => 38317547.188d5a77.js} | 2 +- .../js/{38cf1c7a.5f2325ce.js => 38cf1c7a.b6b75450.js} | 2 +- .../js/{394f3211.5f9ae7dc.js => 394f3211.eb368321.js} | 2 +- .../js/{3b486936.8548a8fa.js => 3b486936.14f5c295.js} | 2 +- .../js/{3d0eb74d.2794d152.js => 3d0eb74d.bfa43dc4.js} | 2 +- .../js/{3d2d0a86.0aa41ecc.js => 3d2d0a86.a5d65979.js} | 2 +- .../js/{3f944aba.f7c92f88.js => 3f944aba.676e3904.js} | 2 +- .../js/{400ddbbb.394e55eb.js => 400ddbbb.39b975fe.js} | 2 +- .../js/{4194805f.7648a706.js => 4194805f.0ac84944.js} | 2 +- .../js/{471c3e37.0732a0f8.js => 471c3e37.24278665.js} | 2 +- .../js/{48fde361.9208d2e3.js => 48fde361.4547b198.js} | 2 +- .../js/{4a060504.c4a87d87.js => 4a060504.ea9289d9.js} | 2 +- .../js/{4a07aaf0.e94a85e6.js => 4a07aaf0.0965b827.js} | 2 +- .../js/{4a2da18c.7c9b1ad0.js => 4a2da18c.e7af6eba.js} | 2 +- .../js/{4aab8b8c.f5a6f07e.js => 4aab8b8c.8dc20bfd.js} | 2 +- .../js/{4bdafdff.ed49ea73.js => 4bdafdff.f95fd3fd.js} | 2 +- .../js/{4c5bf49d.ca1a6b73.js => 4c5bf49d.532665cc.js} | 2 +- .../js/{4c7f7507.0cf12320.js => 4c7f7507.727d59a5.js} | 2 +- .../js/{4d00e03a.c44f7319.js => 4d00e03a.c8d322b7.js} | 2 +- .../js/{4d049718.a2a7e562.js => 4d049718.6b397ac8.js} | 2 +- .../js/{4d68b066.5010896b.js => 4d68b066.da01e2ba.js} | 2 +- .../js/{4dd5816e.b4e531cf.js => 4dd5816e.54289d64.js} | 2 +- .../js/{4dfeb783.57f6daf2.js => 4dfeb783.4e4a50df.js} | 2 +- .../js/{4e1a0951.011df4f3.js => 4e1a0951.a596bbff.js} | 2 +- .../js/{4e73bd72.50ba6eee.js => 4e73bd72.5ec85a48.js} | 2 +- .../js/{4f30448a.ad33b3b8.js => 4f30448a.18e6f579.js} | 2 +- .../js/{4f4b6633.29f9fc0d.js => 4f4b6633.13723156.js} | 2 +- .../js/{4f59166d.37dd0e41.js => 4f59166d.4faf7932.js} | 2 +- .../js/{4f6c3156.6d101bdd.js => 4f6c3156.5ce6d6f5.js} | 2 +- .../js/{50122f86.caa64e70.js => 50122f86.978eb0e7.js} | 2 +- .../js/{504e6c2d.3a3311b1.js => 504e6c2d.5276e8fc.js} | 2 +- .../js/{509d2004.72acb360.js => 509d2004.515098d6.js} | 2 +- .../js/{5285d58e.65ac0f19.js => 5285d58e.dc0572a0.js} | 2 +- .../js/{528fe65e.933eb23e.js => 528fe65e.854670a4.js} | 2 +- .../js/{54c144e4.7075bb1b.js => 54c144e4.849468f9.js} | 2 +- .../js/{54ca8693.51041cd0.js => 54ca8693.48b03a2b.js} | 2 +- .../js/{55c77f1e.fdd2ec15.js => 55c77f1e.445ec78a.js} | 2 +- .../js/{56279b5e.dd772e67.js => 56279b5e.78110a26.js} | 2 +- .../js/{565a5567.7a63f34f.js => 565a5567.42b506a7.js} | 2 +- .../js/{56af72f6.e2be7e89.js => 56af72f6.9a096055.js} | 2 +- .../js/{5792f9ba.5dd35a6d.js => 5792f9ba.782a6cc0.js} | 2 +- .../js/{579cc8d4.75c1db67.js => 579cc8d4.3561f055.js} | 2 +- .../js/{57f5c28c.cedd9438.js => 57f5c28c.1c46cd30.js} | 2 +- .../js/{5881f7ec.fe052764.js => 5881f7ec.cc785cb4.js} | 2 +- .../js/{58d52345.a4dd140c.js => 58d52345.77f848c4.js} | 2 +- .../js/{58e6b30f.bba8a8b5.js => 58e6b30f.cfedeae8.js} | 2 +- .../js/{5945e8b0.756ecc25.js => 5945e8b0.0350a0ee.js} | 2 +- .../js/{59b1a96c.15ebb5a8.js => 59b1a96c.d42a4a6d.js} | 2 +- .../js/{5a9b411c.633128b8.js => 5a9b411c.41ceb8c0.js} | 2 +- .../js/{5bc7272e.2082701e.js => 5bc7272e.2ea91e98.js} | 2 +- .../js/{5d7590c2.400b4369.js => 5d7590c2.f1875551.js} | 2 +- .../js/{5dde70bf.f04f0102.js => 5dde70bf.05ad436e.js} | 2 +- .../js/{5e21a9be.3b1a370a.js => 5e21a9be.07be67d3.js} | 2 +- .../js/{5e352ef4.44328195.js => 5e352ef4.0d71c311.js} | 2 +- .../js/{5e457383.2cb59d7a.js => 5e457383.77ecbaa1.js} | 2 +- .../js/{5ec7a7fe.486c187f.js => 5ec7a7fe.825af4e3.js} | 2 +- .../js/{5ececfab.951bf2be.js => 5ececfab.010d8af8.js} | 2 +- .../js/{5fa4a5b6.58928c34.js => 5fa4a5b6.0b6f7acb.js} | 2 +- .../js/{5ffc8074.c7c46b28.js => 5ffc8074.f8c76d92.js} | 2 +- .../js/{606959d6.3edb8139.js => 606959d6.68fabfee.js} | 2 +- .../js/{60d99771.af816a56.js => 60d99771.8031c813.js} | 2 +- .../js/{610e7425.05ef450c.js => 610e7425.235491b2.js} | 2 +- .../js/{612b773e.977652cc.js => 612b773e.21049eaa.js} | 2 +- .../js/{61595218.ac93c769.js => 61595218.9e0c75e2.js} | 2 +- .../js/{617523b3.8b55c2b2.js => 617523b3.a43a650a.js} | 2 +- .../js/{61c7d915.b1aa6299.js => 61c7d915.04828008.js} | 2 +- .../js/{623b6c78.0172bd69.js => 623b6c78.2900a0a2.js} | 2 +- .../js/{64536e1a.3de73596.js => 64536e1a.9ea966df.js} | 2 +- .../js/{64947e00.fc42f77e.js => 64947e00.15f950f3.js} | 2 +- .../js/{652c74f1.ce075dcf.js => 652c74f1.730aed76.js} | 2 +- .../js/{65b8d1d1.0dfc869c.js => 65b8d1d1.047e76fa.js} | 2 +- .../js/{673df5d6.49bf875e.js => 673df5d6.5d9bf0d1.js} | 2 +- .../js/{68b7d615.f5c917b5.js => 68b7d615.fbdbb964.js} | 2 +- .../js/{68e30702.595a5a15.js => 68e30702.7286fff2.js} | 2 +- .../js/{69f2ab1f.7462786d.js => 69f2ab1f.a3628935.js} | 2 +- .../js/{6a8c9872.66115f2f.js => 6a8c9872.bbc23cdd.js} | 2 +- .../js/{6ad31330.8b092014.js => 6ad31330.6e65fbf9.js} | 2 +- .../js/{6c124661.83050f3b.js => 6c124661.afc216c8.js} | 2 +- .../js/{6c14a231.3eaaec7d.js => 6c14a231.2ab795f0.js} | 2 +- .../js/{6c4340be.2f08e357.js => 6c4340be.43dfaea6.js} | 2 +- .../js/{6c6ce37c.0aab0791.js => 6c6ce37c.8f5946ca.js} | 2 +- .../js/{6cfcfcfb.87fe7d5d.js => 6cfcfcfb.bea63401.js} | 2 +- .../js/{6d89025c.b6142cc1.js => 6d89025c.c42687f4.js} | 2 +- .../js/{6efd6ec9.a781fcb6.js => 6efd6ec9.d16f9c24.js} | 2 +- .../js/{6fe30f11.a00d78f9.js => 6fe30f11.3c83de5b.js} | 2 +- .../js/{71a56230.f3946764.js => 71a56230.2a30f758.js} | 2 +- .../js/{72be5fd7.8272dd94.js => 72be5fd7.847f2c01.js} | 2 +- .../js/{72d0dc3a.74aee997.js => 72d0dc3a.19800fb7.js} | 2 +- .../js/{741df2ae.f14df0a1.js => 741df2ae.ba6bdd66.js} | 2 +- .../js/{74383bd8.10b03c4d.js => 74383bd8.4d536fa8.js} | 2 +- .../js/{7515d7ec.0f02db8c.js => 7515d7ec.500009da.js} | 2 +- .../js/{756c6ac7.d29667a5.js => 756c6ac7.55eadee8.js} | 2 +- .../js/{75cc8326.67419917.js => 75cc8326.0b5dad68.js} | 2 +- .../js/{766e1cc8.2071bfdd.js => 766e1cc8.83bf99b5.js} | 2 +- .../js/{767c28af.9b3f2b00.js => 767c28af.7606b775.js} | 2 +- .../js/{77cdcd82.17a9de0d.js => 77cdcd82.3321530b.js} | 2 +- .../js/{7810a993.304675e2.js => 7810a993.e19c8ecc.js} | 2 +- .../js/{78619623.de4f9ce0.js => 78619623.acbf2cff.js} | 2 +- .../js/{78da31a1.d515a8c1.js => 78da31a1.cf24aae5.js} | 2 +- .../js/{7afb60b1.736e3057.js => 7afb60b1.90db6183.js} | 2 +- .../js/{7b54f5d5.10cce0ec.js => 7b54f5d5.b4c102e0.js} | 2 +- .../js/{7bf967bc.5b329fb7.js => 7bf967bc.ad70e0f4.js} | 2 +- .../js/{7c27e34c.f1aff3c4.js => 7c27e34c.1ac91b53.js} | 2 +- .../js/{7e507331.2d8a1ee0.js => 7e507331.91373eaa.js} | 2 +- .../js/{7e63a40e.32957347.js => 7e63a40e.efdb47b0.js} | 2 +- .../js/{7ee46e43.a30e535b.js => 7ee46e43.c3a02fcd.js} | 2 +- assets/js/8158.5d3c0904.js | 1 + .../js/{820db038.84479dbc.js => 820db038.30040718.js} | 2 +- .../js/{822cd419.ae42397e.js => 822cd419.6ccd8cfe.js} | 2 +- .../js/{82395e72.dc7ee846.js => 82395e72.4e7d40c0.js} | 2 +- .../js/{8299d165.ebbcc1be.js => 8299d165.b0baee98.js} | 2 +- .../js/{843ebfb4.2d883124.js => 843ebfb4.4915cff2.js} | 2 +- .../js/{85339969.50230dc8.js => 85339969.da377a85.js} | 2 +- .../js/{859fcda7.dd618dad.js => 859fcda7.e1a1dfa2.js} | 2 +- .../js/{85c72337.51288902.js => 85c72337.ac0db6d4.js} | 2 +- .../js/{87089bce.fd4ba243.js => 87089bce.44226929.js} | 2 +- .../js/{8913b51a.b8aea398.js => 8913b51a.810e616c.js} | 2 +- .../js/{89cae3a7.5acb06f9.js => 89cae3a7.05b5165d.js} | 2 +- .../js/{89ed63c8.fc9f93b4.js => 89ed63c8.d51a0220.js} | 2 +- .../js/{8b6bafea.4c53dd2a.js => 8b6bafea.69028f9b.js} | 2 +- .../js/{8bf32d27.228eb7dc.js => 8bf32d27.3378bf47.js} | 2 +- .../js/{8c95fc16.b5b3d6f8.js => 8c95fc16.32ab2a8a.js} | 2 +- .../js/{8d81badd.64591638.js => 8d81badd.0c6fdf6d.js} | 2 +- .../js/{8f7abfe1.4744fc25.js => 8f7abfe1.efb01d30.js} | 2 +- .../js/{8f7fa040.1e12c214.js => 8f7fa040.51146fec.js} | 2 +- .../js/{8f951ce3.38a0ed05.js => 8f951ce3.a721f857.js} | 2 +- .../js/{8f967659.f5d72090.js => 8f967659.6ddd2ee0.js} | 2 +- .../js/{9000b231.b513fb14.js => 9000b231.ae4fd67e.js} | 2 +- .../js/{9073923c.5bec0ca2.js => 9073923c.d4d97409.js} | 2 +- .../js/{90e0b7fd.b13844c0.js => 90e0b7fd.c8a731fc.js} | 2 +- .../js/{9206a32f.5e372737.js => 9206a32f.685678c0.js} | 2 +- .../js/{9279cea7.66cbfaa8.js => 9279cea7.f1aa6c8c.js} | 2 +- assets/js/9462.462ab9a2.js | 1 - .../js/{947f2c39.eed5acb7.js => 947f2c39.dd206cf2.js} | 2 +- .../js/{950394a4.7223c23c.js => 950394a4.647f9c57.js} | 2 +- .../js/{95576100.d7e099eb.js => 95576100.7cd86243.js} | 2 +- .../js/{9664ee55.af355be4.js => 9664ee55.54f363d2.js} | 2 +- .../js/{96877411.11d1c90c.js => 96877411.eb9ab217.js} | 2 +- .../js/{9749ab4a.5a374ae4.js => 9749ab4a.8051e985.js} | 2 +- .../js/{976f6afc.1e2e0c2e.js => 976f6afc.00b1895b.js} | 2 +- .../js/{9953ecde.c796fb6a.js => 9953ecde.dc248cb7.js} | 2 +- .../js/{9bd507da.f6812058.js => 9bd507da.92df8552.js} | 2 +- .../js/{9d336ee4.d1d3fbf7.js => 9d336ee4.ee532efb.js} | 2 +- .../js/{9d9f8394.57f61e6c.js => 9d9f8394.fab55070.js} | 2 +- .../js/{9f0ecd2e.246fb3f2.js => 9f0ecd2e.ae5e64e1.js} | 2 +- .../js/{a0bf4a5f.182ebb00.js => a0bf4a5f.74861ab2.js} | 2 +- .../js/{a13f3cdc.e5f640b2.js => a13f3cdc.672b78da.js} | 2 +- .../js/{a16ee953.1dff7035.js => a16ee953.9a68612f.js} | 2 +- .../js/{a1e3d512.f4a5eedb.js => a1e3d512.9293ad8e.js} | 2 +- .../js/{a23a5b68.c6e07c87.js => a23a5b68.b5d885a3.js} | 2 +- .../js/{a264d631.d36b1f3c.js => a264d631.4035d270.js} | 2 +- .../js/{a27ea030.7525b406.js => a27ea030.3bd08244.js} | 2 +- .../js/{a27f6be0.01e05b2b.js => a27f6be0.7a85335b.js} | 2 +- .../js/{a28aff23.d779452d.js => a28aff23.a5c896b5.js} | 2 +- .../js/{a2d3d8d2.6d97a807.js => a2d3d8d2.02d26f89.js} | 2 +- .../js/{a30fd8ca.cc2ee382.js => a30fd8ca.a1a62e13.js} | 2 +- .../js/{a320b509.542f12dd.js => a320b509.90fae613.js} | 2 +- .../js/{a3a193a6.2bb72676.js => a3a193a6.8bfca0ec.js} | 2 +- .../js/{a55b0daf.2eec40d4.js => a55b0daf.d34ec43f.js} | 2 +- .../js/{a9125b44.b87a4fc9.js => a9125b44.9d1e548e.js} | 2 +- .../js/{a91c1a62.c5c73dcf.js => a91c1a62.f4487fce.js} | 2 +- .../js/{a95c9e82.d7bf2a67.js => a95c9e82.600fb4a5.js} | 2 +- .../js/{a99e9943.861d4b51.js => a99e9943.9c2c1474.js} | 2 +- .../js/{a9bc4f03.250477a2.js => a9bc4f03.b3e58655.js} | 2 +- .../js/{aa52484c.045d5777.js => aa52484c.61b177fd.js} | 2 +- .../js/{aa5b6080.673f0a63.js => aa5b6080.381bae1e.js} | 2 +- .../js/{aa675676.dea071fb.js => aa675676.5d9c0e5d.js} | 2 +- .../js/{aba5bf07.b9f64b98.js => aba5bf07.da06d077.js} | 2 +- .../js/{ac8293fa.4b2a6b5f.js => ac8293fa.33c65881.js} | 2 +- .../js/{acbaac14.0e34867d.js => acbaac14.ce7e7bba.js} | 2 +- .../js/{ae0a12ed.e990137c.js => ae0a12ed.108279d2.js} | 2 +- .../js/{aebf35b6.8952f146.js => aebf35b6.7c35d9f1.js} | 2 +- .../js/{b06c439f.ed04ab32.js => b06c439f.1814399b.js} | 2 +- .../js/{b0ed7ea5.73400342.js => b0ed7ea5.59abd713.js} | 2 +- .../js/{b103c05a.66f0d4d1.js => b103c05a.2c121ae6.js} | 2 +- .../js/{b26a5b84.94499d93.js => b26a5b84.c51fb3ba.js} | 2 +- .../js/{b2d9540a.f5cddfbb.js => b2d9540a.2a7a8831.js} | 2 +- .../js/{b35d1284.5b3a1e1c.js => b35d1284.ce1e8749.js} | 2 +- .../js/{b370b50c.24d426da.js => b370b50c.f9e9f1f7.js} | 2 +- .../js/{b42f5805.f52b7c27.js => b42f5805.17436cba.js} | 2 +- .../js/{b4657038.94094889.js => b4657038.b198b399.js} | 2 +- .../js/{b4aea2ce.efb40527.js => b4aea2ce.310f4b7d.js} | 2 +- .../js/{b5d0ac54.11ff1282.js => b5d0ac54.f653835d.js} | 2 +- .../js/{b5d32d98.f0acdc5c.js => b5d32d98.bb7ca4aa.js} | 2 +- .../js/{b6722b03.0f792d4a.js => b6722b03.74eb5003.js} | 2 +- .../js/{b6a6a31f.59d36e4d.js => b6a6a31f.587f0446.js} | 2 +- .../js/{b7442939.95fdc2e7.js => b7442939.8ab388f0.js} | 2 +- .../js/{b8487569.d2bb8d8a.js => b8487569.332c9ebe.js} | 2 +- .../js/{b94a1068.fbbebba7.js => b94a1068.29fbf944.js} | 2 +- .../js/{b9d6d6e5.23ad4e90.js => b9d6d6e5.c8d10d7f.js} | 2 +- .../js/{b9ea999a.a6235f86.js => b9ea999a.708e79ea.js} | 2 +- .../js/{ba7653ad.e90cc1de.js => ba7653ad.982c6c9b.js} | 2 +- .../js/{bb5ef1b7.82e72688.js => bb5ef1b7.80062520.js} | 2 +- .../js/{bb9fe7c3.6af7855f.js => bb9fe7c3.6dd5e99b.js} | 2 +- .../js/{bcb6471f.0644f520.js => bcb6471f.ecbcbd7c.js} | 2 +- .../js/{bcc01c83.3bd62a12.js => bcc01c83.9cc13304.js} | 2 +- .../js/{bd2c4a98.fe752412.js => bd2c4a98.cc91c3c6.js} | 2 +- .../js/{bda39da3.60aa5ceb.js => bda39da3.da603a75.js} | 2 +- .../js/{bdb33130.227c6eff.js => bdb33130.8f685e3c.js} | 2 +- .../js/{be1f0304.ec3e82e5.js => be1f0304.ded0473a.js} | 2 +- .../js/{beccb025.2c15d7a4.js => beccb025.af0246bf.js} | 2 +- .../js/{bf2a5963.e7d07140.js => bf2a5963.c913f417.js} | 2 +- .../js/{c007fb39.ded066fe.js => c007fb39.f54c7d5f.js} | 2 +- .../js/{c0fa6485.d007f2cc.js => c0fa6485.2a80d401.js} | 2 +- .../js/{c10d4a63.bd5619e8.js => c10d4a63.a576095a.js} | 2 +- .../js/{c1fe0282.bb7327d3.js => c1fe0282.1aa8f919.js} | 2 +- .../js/{c275698c.e2579a98.js => c275698c.48993737.js} | 2 +- .../js/{c329487f.bef3f016.js => c329487f.4cfc14e5.js} | 2 +- .../js/{c3701568.6e5085a4.js => c3701568.23c5e54b.js} | 2 +- .../js/{c4d37b36.c7abd8e8.js => c4d37b36.850e299e.js} | 2 +- .../js/{c4f5d8e4.18e9bd0f.js => c4f5d8e4.e0be6be1.js} | 2 +- .../js/{c5fa393d.de7de634.js => c5fa393d.7442ce88.js} | 2 +- .../js/{c69dda99.6259f383.js => c69dda99.bab571e4.js} | 2 +- .../js/{c7a4caa1.d453236b.js => c7a4caa1.9023b5ab.js} | 2 +- .../js/{c7e7ae18.458cd986.js => c7e7ae18.7aac8686.js} | 2 +- .../js/{c8bdc4df.945387d4.js => c8bdc4df.f87624e5.js} | 2 +- .../js/{c933a311.5c9464ab.js => c933a311.32f863e4.js} | 2 +- .../js/{c953ec08.c0dba9ed.js => c953ec08.040e054c.js} | 2 +- .../js/{ca36df4d.aeb4098b.js => ca36df4d.d8c20347.js} | 2 +- .../js/{caa79efa.e50dc4d6.js => caa79efa.48c35a3e.js} | 2 +- .../js/{cb01db44.4b8a5915.js => cb01db44.9d4869e3.js} | 2 +- .../js/{cc08685c.a1c356b7.js => cc08685c.65277fd4.js} | 2 +- .../js/{cc1c02fe.165c033e.js => cc1c02fe.61b2623b.js} | 2 +- .../js/{cc1f18af.1fc35cb3.js => cc1f18af.2f6cfe23.js} | 2 +- .../js/{cd25a595.2ff2dc72.js => cd25a595.ad485f50.js} | 2 +- .../js/{cd30f404.204da6fe.js => cd30f404.69774811.js} | 2 +- .../js/{cd699560.967c131e.js => cd699560.d49156fe.js} | 2 +- .../js/{cddcd4e6.f9de7ce3.js => cddcd4e6.7605ffd6.js} | 2 +- .../js/{ce95b17c.9e8c5589.js => ce95b17c.1c7cb95e.js} | 2 +- .../js/{cf877cff.f8eace7d.js => cf877cff.13543e94.js} | 2 +- assets/js/{common.5c1c7fb8.js => common.fbe21386.js} | 2 +- .../js/{d07ad772.9add3717.js => d07ad772.6107eae2.js} | 2 +- .../js/{d3540d59.647d1389.js => d3540d59.3edc4833.js} | 2 +- .../js/{d4446569.436d2399.js => d4446569.19825daa.js} | 2 +- .../js/{d49884c9.0064b6c0.js => d49884c9.0f84318a.js} | 2 +- .../js/{d4c8693b.e2d78398.js => d4c8693b.85c68619.js} | 2 +- .../js/{d589d3a7.6021e5dd.js => d589d3a7.3ca3db6d.js} | 2 +- .../js/{d6188fd4.104abbfc.js => d6188fd4.84dd5e9f.js} | 2 +- .../js/{d6b4b60c.a2861081.js => d6b4b60c.59d29704.js} | 2 +- .../js/{d7067606.219d83f5.js => d7067606.5ccc11ee.js} | 2 +- .../js/{d8037f4c.8757c4fd.js => d8037f4c.7800ade4.js} | 2 +- .../js/{d8ff000f.45d24dbc.js => d8ff000f.d78bbbdb.js} | 2 +- .../js/{d9523c62.4313544a.js => d9523c62.56e361f4.js} | 2 +- .../js/{db6a6f31.f295f2cd.js => db6a6f31.2ba1f92c.js} | 2 +- .../js/{dbf2bcb3.a3e02c38.js => dbf2bcb3.7545e0e1.js} | 2 +- .../js/{dc9a99e0.feedb86d.js => dc9a99e0.eb3363f2.js} | 2 +- .../js/{e126d786.28afa778.js => e126d786.904fc2ce.js} | 2 +- .../js/{e196b408.8c658db6.js => e196b408.e18e9bb2.js} | 2 +- .../js/{e1b8bb84.e290ba7c.js => e1b8bb84.5df320fe.js} | 2 +- .../js/{e29eb381.4559764e.js => e29eb381.ffc5e4f6.js} | 2 +- .../js/{e2e51976.8fba7e43.js => e2e51976.c6ecb350.js} | 2 +- .../js/{e347e63a.44f49701.js => e347e63a.2ac8076c.js} | 2 +- .../js/{e39283a8.f1377ffd.js => e39283a8.893bcc0e.js} | 2 +- .../js/{e45c611c.fb56a047.js => e45c611c.3998a678.js} | 2 +- .../js/{e4c5fdc3.fb24cd67.js => e4c5fdc3.e955804e.js} | 2 +- .../js/{e5d0e3cb.effe63e0.js => e5d0e3cb.374818b0.js} | 2 +- .../js/{e5d7b215.1e6f5f63.js => e5d7b215.1e815ef0.js} | 2 +- .../js/{e617c97b.d7d19342.js => e617c97b.190d4370.js} | 2 +- .../js/{e63ebe23.e08a4a5e.js => e63ebe23.cfd28ed6.js} | 2 +- .../js/{e6858589.96f3f1d5.js => e6858589.51ed2044.js} | 2 +- .../js/{e688cd7e.d3b2e571.js => e688cd7e.acd459e0.js} | 2 +- .../js/{e68b092b.fbd8b05e.js => e68b092b.91d3b084.js} | 2 +- .../js/{e7672013.22f2c2b2.js => e7672013.9de4c4ac.js} | 2 +- .../js/{e7ffb4b4.0ce9a2e9.js => e7ffb4b4.d76bf1af.js} | 2 +- .../js/{e806c7bf.5077f259.js => e806c7bf.56438157.js} | 2 +- .../js/{e91daeb9.f96f18c1.js => e91daeb9.88be2064.js} | 2 +- assets/js/eaa287f0.30b615dd.js | 1 + assets/js/eaa287f0.760001b8.js | 1 - .../js/{eb333c39.802adc5d.js => eb333c39.de214301.js} | 2 +- .../js/{eca0cf35.d91731cc.js => eca0cf35.ebe74b46.js} | 2 +- .../js/{ece9cf29.b734bc0b.js => ece9cf29.442971fe.js} | 2 +- .../js/{ed0c0463.8c5643c1.js => ed0c0463.18d8bdce.js} | 2 +- .../js/{eec7caa6.259ce3c5.js => eec7caa6.c4e4060d.js} | 2 +- .../js/{f07f4757.612acd46.js => f07f4757.fcb48beb.js} | 2 +- .../js/{f2710c27.ae6a2d96.js => f2710c27.933c6707.js} | 2 +- .../js/{f309eabc.d9e621d8.js => f309eabc.6920a8bb.js} | 2 +- .../js/{f48e2589.2e01819e.js => f48e2589.8017cce0.js} | 2 +- .../js/{f4e1d1ba.b560f451.js => f4e1d1ba.9f7d01d1.js} | 2 +- .../js/{f5b0a435.6436b061.js => f5b0a435.366e2203.js} | 2 +- .../js/{f7c03581.b265745d.js => f7c03581.b555dacd.js} | 2 +- .../js/{f9063551.7a9b33d9.js => f9063551.73e2d873.js} | 2 +- .../js/{f94b062c.123eddb0.js => f94b062c.ccbd99ea.js} | 2 +- .../js/{f9511b3d.4032d80e.js => f9511b3d.cd07b318.js} | 2 +- .../js/{f9af357c.efe0eefa.js => f9af357c.8ec31c6c.js} | 2 +- .../js/{fa1dd05c.e26d512a.js => fa1dd05c.f085bd72.js} | 2 +- .../js/{fa41c0e9.4c091372.js => fa41c0e9.567f235d.js} | 2 +- .../js/{fe153c07.5080ebdd.js => fe153c07.9070bba6.js} | 2 +- assets/js/main.e957a21b.js | 2 -- assets/js/main.f2200491.js | 2 ++ ...21b.js.LICENSE.txt => main.f2200491.js.LICENSE.txt} | 0 ...ntime~main.4f566395.js => runtime~main.1a20f51d.js} | 2 +- docs.html | 10 +++++----- docs/3.0.html | 10 +++++----- docs/3.0/annotations_reference.html | 10 +++++----- docs/3.0/argument-resolving.html | 10 +++++----- docs/3.0/authentication_authorization.html | 10 +++++----- docs/3.0/autowiring.html | 10 +++++----- docs/3.0/custom-output-types.html | 10 +++++----- docs/3.0/custom-types.html | 10 +++++----- docs/3.0/doctrine-annotations-attributes.html | 10 +++++----- docs/3.0/error-handling.html | 10 +++++----- docs/3.0/extend_input_type.html | 10 +++++----- docs/3.0/extend_type.html | 10 +++++----- docs/3.0/external_type_declaration.html | 10 +++++----- docs/3.0/field-middlewares.html | 10 +++++----- docs/3.0/file-uploads.html | 10 +++++----- docs/3.0/fine-grained-security.html | 10 +++++----- docs/3.0/getting-started.html | 10 +++++----- docs/3.0/implementing-security.html | 10 +++++----- docs/3.0/inheritance-interfaces.html | 10 +++++----- docs/3.0/inheritance.html | 10 +++++----- docs/3.0/input-types.html | 10 +++++----- docs/3.0/internals.html | 10 +++++----- docs/3.0/laravel-package-advanced.html | 10 +++++----- docs/3.0/laravel-package.html | 10 +++++----- docs/3.0/migrating.html | 10 +++++----- docs/3.0/multiple_output_types.html | 10 +++++----- docs/3.0/mutations.html | 10 +++++----- docs/3.0/other-frameworks.html | 10 +++++----- docs/3.0/pagination.html | 10 +++++----- docs/3.0/prefetch-method.html | 10 +++++----- docs/3.0/queries.html | 10 +++++----- docs/3.0/query-plan.html | 10 +++++----- docs/3.0/semver.html | 10 +++++----- docs/3.0/symfony-bundle-advanced.html | 10 +++++----- docs/3.0/symfony-bundle.html | 10 +++++----- docs/3.0/troubleshooting.html | 10 +++++----- docs/3.0/type_mapping.html | 10 +++++----- docs/3.0/universal_service_providers.html | 10 +++++----- docs/3.0/validation.html | 10 +++++----- docs/4.0.html | 10 +++++----- docs/4.0/annotations_reference.html | 10 +++++----- docs/4.0/argument-resolving.html | 10 +++++----- docs/4.0/authentication_authorization.html | 10 +++++----- docs/4.0/autowiring.html | 10 +++++----- docs/4.0/changelog.html | 10 +++++----- docs/4.0/custom-output-types.html | 10 +++++----- docs/4.0/custom-types.html | 10 +++++----- docs/4.0/doctrine-annotations-attributes.html | 10 +++++----- docs/4.0/error-handling.html | 10 +++++----- docs/4.0/extend_input_type.html | 10 +++++----- docs/4.0/extend_type.html | 10 +++++----- docs/4.0/external_type_declaration.html | 10 +++++----- docs/4.0/field-middlewares.html | 10 +++++----- docs/4.0/file-uploads.html | 10 +++++----- docs/4.0/fine-grained-security.html | 10 +++++----- docs/4.0/getting-started.html | 10 +++++----- docs/4.0/implementing-security.html | 10 +++++----- docs/4.0/inheritance-interfaces.html | 10 +++++----- docs/4.0/inheritance.html | 10 +++++----- docs/4.0/input-types.html | 10 +++++----- docs/4.0/internals.html | 10 +++++----- docs/4.0/laravel-package-advanced.html | 10 +++++----- docs/4.0/laravel-package.html | 10 +++++----- docs/4.0/migrating.html | 10 +++++----- docs/4.0/multiple_output_types.html | 10 +++++----- docs/4.0/mutations.html | 10 +++++----- docs/4.0/other-frameworks.html | 10 +++++----- docs/4.0/pagination.html | 10 +++++----- docs/4.0/prefetch-method.html | 10 +++++----- docs/4.0/queries.html | 10 +++++----- docs/4.0/query-plan.html | 10 +++++----- docs/4.0/semver.html | 10 +++++----- docs/4.0/symfony-bundle-advanced.html | 10 +++++----- docs/4.0/symfony-bundle.html | 10 +++++----- docs/4.0/troubleshooting.html | 10 +++++----- docs/4.0/type_mapping.html | 10 +++++----- docs/4.0/universal_service_providers.html | 10 +++++----- docs/4.0/validation.html | 10 +++++----- docs/4.1.html | 10 +++++----- docs/4.1/annotations_reference.html | 10 +++++----- docs/4.1/argument-resolving.html | 10 +++++----- docs/4.1/authentication_authorization.html | 10 +++++----- docs/4.1/autowiring.html | 10 +++++----- docs/4.1/changelog.html | 10 +++++----- docs/4.1/custom-output-types.html | 10 +++++----- docs/4.1/custom-types.html | 10 +++++----- docs/4.1/doctrine-annotations-attributes.html | 10 +++++----- docs/4.1/error-handling.html | 10 +++++----- docs/4.1/extend_input_type.html | 10 +++++----- docs/4.1/extend_type.html | 10 +++++----- docs/4.1/external_type_declaration.html | 10 +++++----- docs/4.1/field-middlewares.html | 10 +++++----- docs/4.1/file-uploads.html | 10 +++++----- docs/4.1/fine-grained-security.html | 10 +++++----- docs/4.1/getting-started.html | 10 +++++----- docs/4.1/implementing-security.html | 10 +++++----- docs/4.1/inheritance-interfaces.html | 10 +++++----- docs/4.1/inheritance.html | 10 +++++----- docs/4.1/input-types.html | 10 +++++----- docs/4.1/internals.html | 10 +++++----- docs/4.1/laravel-package-advanced.html | 10 +++++----- docs/4.1/laravel-package.html | 10 +++++----- docs/4.1/migrating.html | 10 +++++----- docs/4.1/multiple_output_types.html | 10 +++++----- docs/4.1/mutations.html | 10 +++++----- docs/4.1/other-frameworks.html | 10 +++++----- docs/4.1/pagination.html | 10 +++++----- docs/4.1/prefetch-method.html | 10 +++++----- docs/4.1/queries.html | 10 +++++----- docs/4.1/query-plan.html | 10 +++++----- docs/4.1/semver.html | 10 +++++----- docs/4.1/symfony-bundle-advanced.html | 10 +++++----- docs/4.1/symfony-bundle.html | 10 +++++----- docs/4.1/troubleshooting.html | 10 +++++----- docs/4.1/type_mapping.html | 10 +++++----- docs/4.1/universal_service_providers.html | 10 +++++----- docs/4.1/validation.html | 10 +++++----- docs/4.2.html | 10 +++++----- docs/4.2/annotations-reference.html | 10 +++++----- docs/4.2/argument-resolving.html | 10 +++++----- docs/4.2/authentication-authorization.html | 10 +++++----- docs/4.2/autowiring.html | 10 +++++----- docs/4.2/changelog.html | 10 +++++----- docs/4.2/custom-types.html | 10 +++++----- docs/4.2/doctrine-annotations-attributes.html | 10 +++++----- docs/4.2/error-handling.html | 10 +++++----- docs/4.2/extend-input-type.html | 10 +++++----- docs/4.2/extend-type.html | 10 +++++----- docs/4.2/external-type-declaration.html | 10 +++++----- docs/4.2/field-middlewares.html | 10 +++++----- docs/4.2/file-uploads.html | 10 +++++----- docs/4.2/fine-grained-security.html | 10 +++++----- docs/4.2/getting-started.html | 10 +++++----- docs/4.2/implementing-security.html | 10 +++++----- docs/4.2/inheritance-interfaces.html | 10 +++++----- docs/4.2/input-types.html | 10 +++++----- docs/4.2/internals.html | 10 +++++----- docs/4.2/laravel-package-advanced.html | 10 +++++----- docs/4.2/laravel-package.html | 10 +++++----- docs/4.2/migrating.html | 10 +++++----- docs/4.2/multiple-output-types.html | 10 +++++----- docs/4.2/mutations.html | 10 +++++----- docs/4.2/other-frameworks.html | 10 +++++----- docs/4.2/pagination.html | 10 +++++----- docs/4.2/prefetch-method.html | 10 +++++----- docs/4.2/queries.html | 10 +++++----- docs/4.2/query-plan.html | 10 +++++----- docs/4.2/semver.html | 10 +++++----- docs/4.2/symfony-bundle-advanced.html | 10 +++++----- docs/4.2/symfony-bundle.html | 10 +++++----- docs/4.2/troubleshooting.html | 10 +++++----- docs/4.2/type-mapping.html | 10 +++++----- docs/4.2/universal-service-providers.html | 10 +++++----- docs/4.2/validation.html | 10 +++++----- docs/4.3.html | 10 +++++----- docs/4.3/annotations-reference.html | 10 +++++----- docs/4.3/argument-resolving.html | 10 +++++----- docs/4.3/authentication-authorization.html | 10 +++++----- docs/4.3/autowiring.html | 10 +++++----- docs/4.3/changelog.html | 10 +++++----- docs/4.3/custom-types.html | 10 +++++----- docs/4.3/doctrine-annotations-attributes.html | 10 +++++----- docs/4.3/error-handling.html | 10 +++++----- docs/4.3/extend-input-type.html | 10 +++++----- docs/4.3/extend-type.html | 10 +++++----- docs/4.3/external-type-declaration.html | 10 +++++----- docs/4.3/field-middlewares.html | 10 +++++----- docs/4.3/file-uploads.html | 10 +++++----- docs/4.3/fine-grained-security.html | 10 +++++----- docs/4.3/getting-started.html | 10 +++++----- docs/4.3/implementing-security.html | 10 +++++----- docs/4.3/inheritance-interfaces.html | 10 +++++----- docs/4.3/input-types.html | 10 +++++----- docs/4.3/internals.html | 10 +++++----- docs/4.3/laravel-package-advanced.html | 10 +++++----- docs/4.3/laravel-package.html | 10 +++++----- docs/4.3/migrating.html | 10 +++++----- docs/4.3/multiple-output-types.html | 10 +++++----- docs/4.3/mutations.html | 10 +++++----- docs/4.3/other-frameworks.html | 10 +++++----- docs/4.3/pagination.html | 10 +++++----- docs/4.3/prefetch-method.html | 10 +++++----- docs/4.3/queries.html | 10 +++++----- docs/4.3/query-plan.html | 10 +++++----- docs/4.3/semver.html | 10 +++++----- docs/4.3/symfony-bundle-advanced.html | 10 +++++----- docs/4.3/symfony-bundle.html | 10 +++++----- docs/4.3/troubleshooting.html | 10 +++++----- docs/4.3/type-mapping.html | 10 +++++----- docs/4.3/universal-service-providers.html | 10 +++++----- docs/4.3/validation.html | 10 +++++----- docs/5.0.html | 10 +++++----- docs/5.0/annotations-reference.html | 10 +++++----- docs/5.0/argument-resolving.html | 10 +++++----- docs/5.0/authentication-authorization.html | 10 +++++----- docs/5.0/autowiring.html | 10 +++++----- docs/5.0/changelog.html | 10 +++++----- docs/5.0/custom-types.html | 10 +++++----- docs/5.0/doctrine-annotations-attributes.html | 10 +++++----- docs/5.0/error-handling.html | 10 +++++----- docs/5.0/extend-input-type.html | 10 +++++----- docs/5.0/extend-type.html | 10 +++++----- docs/5.0/external-type-declaration.html | 10 +++++----- docs/5.0/field-middlewares.html | 10 +++++----- docs/5.0/file-uploads.html | 10 +++++----- docs/5.0/fine-grained-security.html | 10 +++++----- docs/5.0/getting-started.html | 10 +++++----- docs/5.0/implementing-security.html | 10 +++++----- docs/5.0/inheritance-interfaces.html | 10 +++++----- docs/5.0/input-types.html | 10 +++++----- docs/5.0/internals.html | 10 +++++----- docs/5.0/laravel-package-advanced.html | 10 +++++----- docs/5.0/laravel-package.html | 10 +++++----- docs/5.0/migrating.html | 10 +++++----- docs/5.0/multiple-output-types.html | 10 +++++----- docs/5.0/mutations.html | 10 +++++----- docs/5.0/other-frameworks.html | 10 +++++----- docs/5.0/pagination.html | 10 +++++----- docs/5.0/prefetch-method.html | 10 +++++----- docs/5.0/queries.html | 10 +++++----- docs/5.0/query-plan.html | 10 +++++----- docs/5.0/semver.html | 10 +++++----- docs/5.0/symfony-bundle-advanced.html | 10 +++++----- docs/5.0/symfony-bundle.html | 10 +++++----- docs/5.0/troubleshooting.html | 10 +++++----- docs/5.0/type-mapping.html | 10 +++++----- docs/5.0/universal-service-providers.html | 10 +++++----- docs/5.0/validation.html | 10 +++++----- docs/6.0.html | 10 +++++----- docs/6.0/annotations-reference.html | 10 +++++----- docs/6.0/argument-resolving.html | 10 +++++----- docs/6.0/authentication-authorization.html | 10 +++++----- docs/6.0/autowiring.html | 10 +++++----- docs/6.0/changelog.html | 10 +++++----- docs/6.0/custom-types.html | 10 +++++----- docs/6.0/doctrine-annotations-attributes.html | 10 +++++----- docs/6.0/error-handling.html | 10 +++++----- docs/6.0/extend-input-type.html | 10 +++++----- docs/6.0/extend-type.html | 10 +++++----- docs/6.0/external-type-declaration.html | 10 +++++----- docs/6.0/field-middlewares.html | 10 +++++----- docs/6.0/file-uploads.html | 10 +++++----- docs/6.0/fine-grained-security.html | 10 +++++----- docs/6.0/getting-started.html | 10 +++++----- docs/6.0/implementing-security.html | 10 +++++----- docs/6.0/inheritance-interfaces.html | 10 +++++----- docs/6.0/input-types.html | 10 +++++----- docs/6.0/internals.html | 10 +++++----- docs/6.0/laravel-package-advanced.html | 10 +++++----- docs/6.0/laravel-package.html | 10 +++++----- docs/6.0/migrating.html | 10 +++++----- docs/6.0/multiple-output-types.html | 10 +++++----- docs/6.0/mutations.html | 10 +++++----- docs/6.0/other-frameworks.html | 10 +++++----- docs/6.0/pagination.html | 10 +++++----- docs/6.0/prefetch-method.html | 10 +++++----- docs/6.0/queries.html | 10 +++++----- docs/6.0/query-plan.html | 10 +++++----- docs/6.0/semver.html | 10 +++++----- docs/6.0/symfony-bundle-advanced.html | 10 +++++----- docs/6.0/symfony-bundle.html | 10 +++++----- docs/6.0/troubleshooting.html | 10 +++++----- docs/6.0/type-mapping.html | 10 +++++----- docs/6.0/universal-service-providers.html | 10 +++++----- docs/6.0/validation.html | 10 +++++----- docs/6.1.html | 10 +++++----- docs/6.1/annotations-reference.html | 10 +++++----- docs/6.1/argument-resolving.html | 10 +++++----- docs/6.1/authentication-authorization.html | 10 +++++----- docs/6.1/autowiring.html | 10 +++++----- docs/6.1/changelog.html | 10 +++++----- docs/6.1/custom-types.html | 10 +++++----- docs/6.1/doctrine-annotations-attributes.html | 10 +++++----- docs/6.1/error-handling.html | 10 +++++----- docs/6.1/extend-input-type.html | 10 +++++----- docs/6.1/extend-type.html | 10 +++++----- docs/6.1/external-type-declaration.html | 10 +++++----- docs/6.1/field-middlewares.html | 10 +++++----- docs/6.1/file-uploads.html | 10 +++++----- docs/6.1/fine-grained-security.html | 10 +++++----- docs/6.1/getting-started.html | 10 +++++----- docs/6.1/implementing-security.html | 10 +++++----- docs/6.1/inheritance-interfaces.html | 10 +++++----- docs/6.1/input-types.html | 10 +++++----- docs/6.1/internals.html | 10 +++++----- docs/6.1/laravel-package-advanced.html | 10 +++++----- docs/6.1/laravel-package.html | 10 +++++----- docs/6.1/migrating.html | 10 +++++----- docs/6.1/multiple-output-types.html | 10 +++++----- docs/6.1/mutations.html | 10 +++++----- docs/6.1/other-frameworks.html | 10 +++++----- docs/6.1/pagination.html | 10 +++++----- docs/6.1/prefetch-method.html | 10 +++++----- docs/6.1/queries.html | 10 +++++----- docs/6.1/query-plan.html | 10 +++++----- docs/6.1/semver.html | 10 +++++----- docs/6.1/symfony-bundle-advanced.html | 10 +++++----- docs/6.1/symfony-bundle.html | 10 +++++----- docs/6.1/troubleshooting.html | 10 +++++----- docs/6.1/type-mapping.html | 10 +++++----- docs/6.1/universal-service-providers.html | 10 +++++----- docs/6.1/validation.html | 10 +++++----- docs/annotations-reference.html | 10 +++++----- docs/argument-resolving.html | 10 +++++----- docs/authentication-authorization.html | 10 +++++----- docs/automatic-persisted-queries.html | 10 +++++----- docs/autowiring.html | 10 +++++----- docs/changelog.html | 10 +++++----- docs/custom-types.html | 10 +++++----- docs/doctrine-annotations-attributes.html | 10 +++++----- docs/error-handling.html | 10 +++++----- docs/extend-input-type.html | 10 +++++----- docs/extend-type.html | 10 +++++----- docs/external-type-declaration.html | 10 +++++----- docs/field-middlewares.html | 10 +++++----- docs/file-uploads.html | 10 +++++----- docs/fine-grained-security.html | 10 +++++----- docs/getting-started.html | 10 +++++----- docs/implementing-security.html | 10 +++++----- docs/inheritance-interfaces.html | 10 +++++----- docs/input-types.html | 10 +++++----- docs/internals.html | 10 +++++----- docs/laravel-package-advanced.html | 10 +++++----- docs/laravel-package.html | 10 +++++----- docs/migrating.html | 10 +++++----- docs/multiple-output-types.html | 10 +++++----- docs/mutations.html | 10 +++++----- docs/next.html | 10 +++++----- docs/next/annotations-reference.html | 10 +++++----- docs/next/argument-resolving.html | 10 +++++----- docs/next/authentication-authorization.html | 10 +++++----- docs/next/automatic-persisted-queries.html | 10 +++++----- docs/next/autowiring.html | 10 +++++----- docs/next/changelog.html | 10 +++++----- docs/next/custom-types.html | 10 +++++----- docs/next/doctrine-annotations-attributes.html | 10 +++++----- docs/next/error-handling.html | 10 +++++----- docs/next/extend-input-type.html | 10 +++++----- docs/next/extend-type.html | 10 +++++----- docs/next/external-type-declaration.html | 10 +++++----- docs/next/field-middlewares.html | 10 +++++----- docs/next/file-uploads.html | 10 +++++----- docs/next/fine-grained-security.html | 10 +++++----- docs/next/getting-started.html | 10 +++++----- docs/next/implementing-security.html | 10 +++++----- docs/next/inheritance-interfaces.html | 10 +++++----- docs/next/input-types.html | 10 +++++----- docs/next/internals.html | 10 +++++----- docs/next/laravel-package-advanced.html | 10 +++++----- docs/next/laravel-package.html | 10 +++++----- docs/next/migrating.html | 10 +++++----- docs/next/multiple-output-types.html | 10 +++++----- docs/next/mutations.html | 10 +++++----- docs/next/operation-complexity.html | 10 +++++----- docs/next/other-frameworks.html | 10 +++++----- docs/next/pagination.html | 10 +++++----- docs/next/prefetch-method.html | 10 +++++----- docs/next/queries.html | 10 +++++----- docs/next/query-plan.html | 10 +++++----- docs/next/semver.html | 10 +++++----- docs/next/subscriptions.html | 10 +++++----- docs/next/symfony-bundle-advanced.html | 10 +++++----- docs/next/symfony-bundle.html | 10 +++++----- docs/next/troubleshooting.html | 10 +++++----- docs/next/type-mapping.html | 10 +++++----- docs/next/universal-service-providers.html | 10 +++++----- docs/next/validation.html | 10 +++++----- docs/operation-complexity.html | 10 +++++----- docs/other-frameworks.html | 10 +++++----- docs/pagination.html | 10 +++++----- docs/prefetch-method.html | 10 +++++----- docs/queries.html | 10 +++++----- docs/query-plan.html | 10 +++++----- docs/semver.html | 10 +++++----- docs/subscriptions.html | 10 +++++----- docs/symfony-bundle-advanced.html | 10 +++++----- docs/symfony-bundle.html | 10 +++++----- docs/troubleshooting.html | 10 +++++----- docs/type-mapping.html | 10 +++++----- docs/universal-service-providers.html | 10 +++++----- docs/validation.html | 10 +++++----- index.html | 8 ++++---- search.html | 8 ++++---- 780 files changed, 2309 insertions(+), 2309 deletions(-) rename assets/js/{01fe3043.3fad64b7.js => 01fe3043.57dca576.js} (98%) rename assets/js/{029c6d75.43d4c2f1.js => 029c6d75.6e76e079.js} (99%) rename assets/js/{02c5a8b1.35dbaf12.js => 02c5a8b1.ca45c617.js} (98%) rename assets/js/{0343976d.d295fc38.js => 0343976d.dc3209ed.js} (92%) rename assets/js/{0370fbfb.5a5b55ad.js => 0370fbfb.b873a3e9.js} (83%) rename assets/js/{03abab96.4f809df3.js => 03abab96.1c428084.js} (96%) rename assets/js/{03c886f6.b5776fa5.js => 03c886f6.b2d0817c.js} (96%) rename assets/js/{05e8cfc0.14924a11.js => 05e8cfc0.7b22ffae.js} (92%) rename assets/js/{05fed6b1.5b1f7b07.js => 05fed6b1.50b5309d.js} (73%) rename assets/js/{06c02cc7.5a703743.js => 06c02cc7.8457a813.js} (92%) rename assets/js/{07623f9a.e42afba5.js => 07623f9a.3b28ef48.js} (98%) rename assets/js/{07666c14.6ed70fed.js => 07666c14.64d0038d.js} (97%) rename assets/js/{077a13b8.95292adb.js => 077a13b8.3f86c654.js} (98%) rename assets/js/{079b0d3e.b634668e.js => 079b0d3e.6516370a.js} (99%) rename assets/js/{07c49ebd.cbc22070.js => 07c49ebd.f683e66a.js} (94%) rename assets/js/{085c135f.dccfc8aa.js => 085c135f.a8d57bf6.js} (88%) rename assets/js/{08fe23a4.fd364b45.js => 08fe23a4.be9b2800.js} (83%) rename assets/js/{0a57d896.019ac7d9.js => 0a57d896.e912e3ff.js} (99%) rename assets/js/{0cb7e976.b7d4f44b.js => 0cb7e976.2f6d64b5.js} (98%) rename assets/js/{0d7bb119.295445f4.js => 0d7bb119.c337f7c6.js} (98%) rename assets/js/{0db009bb.2dc8137c.js => 0db009bb.5e17f885.js} (83%) rename assets/js/{0db959c8.0e2c30f2.js => 0db959c8.fb424422.js} (93%) rename assets/js/{0df2ba32.2e7b3274.js => 0df2ba32.64b69ad9.js} (97%) rename assets/js/{0e5befdb.eb114fa3.js => 0e5befdb.28a1500f.js} (98%) rename assets/js/{0ef60658.e8767085.js => 0ef60658.bf770bdb.js} (99%) rename assets/js/{0fd21208.36dd45ca.js => 0fd21208.a724c6b4.js} (80%) rename assets/js/{102de343.a2b42f70.js => 102de343.1b48167b.js} (98%) rename assets/js/{107b7a36.0c653dcc.js => 107b7a36.f7b0c4e9.js} (98%) rename assets/js/{107d11ee.272fa44b.js => 107d11ee.e951c451.js} (92%) rename assets/js/{12d3ef9e.563d2fda.js => 12d3ef9e.e80151c3.js} (98%) rename assets/js/{136c1ee9.c16d163a.js => 136c1ee9.5b3b5c84.js} (99%) rename assets/js/{13b4aeb1.56d9e47f.js => 13b4aeb1.d2956e3d.js} (96%) rename assets/js/{1428bdad.9d14ab8c.js => 1428bdad.014df04c.js} (94%) rename assets/js/{143f7888.154e6dae.js => 143f7888.0030da3c.js} (98%) rename assets/js/{15a79915.fee0e891.js => 15a79915.9786fff1.js} (99%) rename assets/js/{15b5a907.83328341.js => 15b5a907.5de5d297.js} (98%) rename assets/js/{16017aa6.d535d4c6.js => 16017aa6.25f7826c.js} (99%) rename assets/js/{16565e6a.23cc4bae.js => 16565e6a.6e039561.js} (98%) rename assets/js/{17518879.44a8bc9b.js => 17518879.07b10d98.js} (98%) rename assets/js/{1774.8c292c27.js => 1774.9b58a5d2.js} (93%) rename assets/js/{17cca601.ca1dfe7d.js => 17cca601.e153a41a.js} (98%) rename assets/js/{18100524.c086b122.js => 18100524.e30015f9.js} (99%) rename assets/js/{1891fd2b.646738f3.js => 1891fd2b.b4b19628.js} (99%) rename assets/js/{18d6c9c9.74a8728d.js => 18d6c9c9.8e7d66c1.js} (98%) delete mode 100644 assets/js/1a4e3797.0725399b.js delete mode 100644 assets/js/1a4e3797.0725399b.js.LICENSE.txt create mode 100644 assets/js/1a4e3797.491d505b.js create mode 100644 assets/js/1a4e3797.491d505b.js.LICENSE.txt rename assets/js/{1aa05129.30e43841.js => 1aa05129.8d91a7ea.js} (89%) rename assets/js/{1af245cd.0b878aaa.js => 1af245cd.ceaf76e0.js} (93%) rename assets/js/{1b1927f4.70837fdd.js => 1b1927f4.999e377f.js} (52%) rename assets/js/{1ba75d10.077c9e1f.js => 1ba75d10.f25708bf.js} (98%) rename assets/js/{1be78505.9bd92206.js => 1be78505.f41f285f.js} (98%) rename assets/js/{1ca907c0.8af28e47.js => 1ca907c0.475dd85b.js} (96%) rename assets/js/{1d20a4b3.a82a7192.js => 1d20a4b3.3ae04557.js} (99%) rename assets/js/{1d703573.f3aa0b11.js => 1d703573.c56e80a3.js} (83%) rename assets/js/{1e2c5f46.3a7a0bf9.js => 1e2c5f46.2622c239.js} (97%) rename assets/js/{1e6ec01e.8ee3df72.js => 1e6ec01e.b01c0803.js} (98%) rename assets/js/{1e7fe27e.6683c9a6.js => 1e7fe27e.1eb07132.js} (72%) rename assets/js/{1ea13486.7fc84ccf.js => 1ea13486.10865491.js} (94%) rename assets/js/{1edb88e5.3fcd7961.js => 1edb88e5.24a4d76c.js} (98%) rename assets/js/{1f5af0f2.db54f2d9.js => 1f5af0f2.918dcc76.js} (97%) rename assets/js/{1f5e9707.9f12226d.js => 1f5e9707.816a8926.js} (94%) rename assets/js/{2014e4e3.cdee6c53.js => 2014e4e3.d25fa2df.js} (96%) rename assets/js/{20540af3.a43cc815.js => 20540af3.26b7ee01.js} (99%) rename assets/js/{21637dff.b4c6abe5.js => 21637dff.327925d3.js} (94%) rename assets/js/{21a7a3b0.b7e5fa05.js => 21a7a3b0.f2e2fc55.js} (58%) rename assets/js/{21cde469.af4cb392.js => 21cde469.97a411b3.js} (99%) rename assets/js/{22e1e32f.891fb0d4.js => 22e1e32f.fd1edf1e.js} (98%) rename assets/js/{232afa3a.e9a3d7db.js => 232afa3a.77394b96.js} (89%) rename assets/js/{2355609d.3e569e61.js => 2355609d.ff4b6802.js} (92%) rename assets/js/{23794275.2ada9f7e.js => 23794275.9a8d8d95.js} (99%) rename assets/js/{23a8ac29.b99ca107.js => 23a8ac29.8abcea32.js} (99%) rename assets/js/{23f642f2.30bbac0d.js => 23f642f2.3e877c32.js} (99%) rename assets/js/{242d99d9.589df066.js => 242d99d9.86bda269.js} (89%) rename assets/js/{24ac61c7.cd8e555e.js => 24ac61c7.52eb9adb.js} (96%) rename assets/js/{24aca886.954f6b8f.js => 24aca886.82ecee1c.js} (96%) rename assets/js/{25d4129e.4553cb4f.js => 25d4129e.6d8bd7b7.js} (99%) rename assets/js/{263ebc7a.2e214490.js => 263ebc7a.c6dc1aef.js} (98%) rename assets/js/{26662da3.26149760.js => 26662da3.da5a7b07.js} (96%) rename assets/js/{26a27afb.fd1bb7ed.js => 26a27afb.acfabf90.js} (98%) rename assets/js/{27258a7d.7cbb6f26.js => 27258a7d.5e5152b8.js} (98%) rename assets/js/{27b414e3.e46c4fea.js => 27b414e3.549f2a3c.js} (99%) rename assets/js/{28c12eaf.e0ba58bd.js => 28c12eaf.77b5e230.js} (99%) rename assets/js/{2917b31e.d68b38c9.js => 2917b31e.e312ae9c.js} (99%) rename assets/js/{29a6c1ba.6de15e74.js => 29a6c1ba.80f0a1f0.js} (99%) rename assets/js/{29cf2ad6.20b4bd04.js => 29cf2ad6.30734bf9.js} (98%) rename assets/js/{2b26025e.fc3ba436.js => 2b26025e.e6842bcd.js} (60%) rename assets/js/{2bbfc5d5.21599b38.js => 2bbfc5d5.5e6bcac0.js} (91%) rename assets/js/{2d02c83c.ff4ec7ba.js => 2d02c83c.abba1f2c.js} (98%) rename assets/js/{2d4548df.8a0eaadc.js => 2d4548df.098efc22.js} (98%) rename assets/js/{2e25c87f.3d095e48.js => 2e25c87f.a3639132.js} (94%) rename assets/js/{2e301473.75e9b1f9.js => 2e301473.0e9607a9.js} (99%) rename assets/js/{2ef99682.38a680cf.js => 2ef99682.ef9dafe1.js} (98%) rename assets/js/{2f36012a.a4789889.js => 2f36012a.20cd1d0e.js} (89%) rename assets/js/{30940d42.1645291e.js => 30940d42.eade2ecb.js} (98%) rename assets/js/{31b4e903.05b641cb.js => 31b4e903.6de1dddd.js} (83%) rename assets/js/{323a980a.4cafa56c.js => 323a980a.ec878f2d.js} (63%) rename assets/js/{332827b4.28485cff.js => 332827b4.a14565af.js} (63%) rename assets/js/{346bcb92.3d2a49dd.js => 346bcb92.db7b61ce.js} (98%) rename assets/js/{354a9b78.bf15845e.js => 354a9b78.31211d7c.js} (97%) rename assets/js/{366cfce3.dba6ab85.js => 366cfce3.cb9991ef.js} (99%) rename assets/js/{36ddade1.44eb30cc.js => 36ddade1.67fe7148.js} (83%) rename assets/js/{379bfe51.3af3b9e7.js => 379bfe51.971c848c.js} (98%) rename assets/js/{380575ae.84b8cf16.js => 380575ae.4091e635.js} (98%) rename assets/js/{38317547.182d255d.js => 38317547.188d5a77.js} (96%) rename assets/js/{38cf1c7a.5f2325ce.js => 38cf1c7a.b6b75450.js} (97%) rename assets/js/{394f3211.5f9ae7dc.js => 394f3211.eb368321.js} (98%) rename assets/js/{3b486936.8548a8fa.js => 3b486936.14f5c295.js} (88%) rename assets/js/{3d0eb74d.2794d152.js => 3d0eb74d.bfa43dc4.js} (98%) rename assets/js/{3d2d0a86.0aa41ecc.js => 3d2d0a86.a5d65979.js} (99%) rename assets/js/{3f944aba.f7c92f88.js => 3f944aba.676e3904.js} (92%) rename assets/js/{400ddbbb.394e55eb.js => 400ddbbb.39b975fe.js} (71%) rename assets/js/{4194805f.7648a706.js => 4194805f.0ac84944.js} (98%) rename assets/js/{471c3e37.0732a0f8.js => 471c3e37.24278665.js} (98%) rename assets/js/{48fde361.9208d2e3.js => 48fde361.4547b198.js} (99%) rename assets/js/{4a060504.c4a87d87.js => 4a060504.ea9289d9.js} (98%) rename assets/js/{4a07aaf0.e94a85e6.js => 4a07aaf0.0965b827.js} (98%) rename assets/js/{4a2da18c.7c9b1ad0.js => 4a2da18c.e7af6eba.js} (98%) rename assets/js/{4aab8b8c.f5a6f07e.js => 4aab8b8c.8dc20bfd.js} (98%) rename assets/js/{4bdafdff.ed49ea73.js => 4bdafdff.f95fd3fd.js} (99%) rename assets/js/{4c5bf49d.ca1a6b73.js => 4c5bf49d.532665cc.js} (70%) rename assets/js/{4c7f7507.0cf12320.js => 4c7f7507.727d59a5.js} (99%) rename assets/js/{4d00e03a.c44f7319.js => 4d00e03a.c8d322b7.js} (98%) rename assets/js/{4d049718.a2a7e562.js => 4d049718.6b397ac8.js} (99%) rename assets/js/{4d68b066.5010896b.js => 4d68b066.da01e2ba.js} (92%) rename assets/js/{4dd5816e.b4e531cf.js => 4dd5816e.54289d64.js} (99%) rename assets/js/{4dfeb783.57f6daf2.js => 4dfeb783.4e4a50df.js} (99%) rename assets/js/{4e1a0951.011df4f3.js => 4e1a0951.a596bbff.js} (99%) rename assets/js/{4e73bd72.50ba6eee.js => 4e73bd72.5ec85a48.js} (80%) rename assets/js/{4f30448a.ad33b3b8.js => 4f30448a.18e6f579.js} (99%) rename assets/js/{4f4b6633.29f9fc0d.js => 4f4b6633.13723156.js} (99%) rename assets/js/{4f59166d.37dd0e41.js => 4f59166d.4faf7932.js} (99%) rename assets/js/{4f6c3156.6d101bdd.js => 4f6c3156.5ce6d6f5.js} (98%) rename assets/js/{50122f86.caa64e70.js => 50122f86.978eb0e7.js} (54%) rename assets/js/{504e6c2d.3a3311b1.js => 504e6c2d.5276e8fc.js} (89%) rename assets/js/{509d2004.72acb360.js => 509d2004.515098d6.js} (96%) rename assets/js/{5285d58e.65ac0f19.js => 5285d58e.dc0572a0.js} (99%) rename assets/js/{528fe65e.933eb23e.js => 528fe65e.854670a4.js} (99%) rename assets/js/{54c144e4.7075bb1b.js => 54c144e4.849468f9.js} (96%) rename assets/js/{54ca8693.51041cd0.js => 54ca8693.48b03a2b.js} (97%) rename assets/js/{55c77f1e.fdd2ec15.js => 55c77f1e.445ec78a.js} (97%) rename assets/js/{56279b5e.dd772e67.js => 56279b5e.78110a26.js} (99%) rename assets/js/{565a5567.7a63f34f.js => 565a5567.42b506a7.js} (97%) rename assets/js/{56af72f6.e2be7e89.js => 56af72f6.9a096055.js} (99%) rename assets/js/{5792f9ba.5dd35a6d.js => 5792f9ba.782a6cc0.js} (73%) rename assets/js/{579cc8d4.75c1db67.js => 579cc8d4.3561f055.js} (90%) rename assets/js/{57f5c28c.cedd9438.js => 57f5c28c.1c46cd30.js} (99%) rename assets/js/{5881f7ec.fe052764.js => 5881f7ec.cc785cb4.js} (99%) rename assets/js/{58d52345.a4dd140c.js => 58d52345.77f848c4.js} (87%) rename assets/js/{58e6b30f.bba8a8b5.js => 58e6b30f.cfedeae8.js} (96%) rename assets/js/{5945e8b0.756ecc25.js => 5945e8b0.0350a0ee.js} (98%) rename assets/js/{59b1a96c.15ebb5a8.js => 59b1a96c.d42a4a6d.js} (95%) rename assets/js/{5a9b411c.633128b8.js => 5a9b411c.41ceb8c0.js} (96%) rename assets/js/{5bc7272e.2082701e.js => 5bc7272e.2ea91e98.js} (98%) rename assets/js/{5d7590c2.400b4369.js => 5d7590c2.f1875551.js} (90%) rename assets/js/{5dde70bf.f04f0102.js => 5dde70bf.05ad436e.js} (98%) rename assets/js/{5e21a9be.3b1a370a.js => 5e21a9be.07be67d3.js} (98%) rename assets/js/{5e352ef4.44328195.js => 5e352ef4.0d71c311.js} (99%) rename assets/js/{5e457383.2cb59d7a.js => 5e457383.77ecbaa1.js} (97%) rename assets/js/{5ec7a7fe.486c187f.js => 5ec7a7fe.825af4e3.js} (99%) rename assets/js/{5ececfab.951bf2be.js => 5ececfab.010d8af8.js} (99%) rename assets/js/{5fa4a5b6.58928c34.js => 5fa4a5b6.0b6f7acb.js} (99%) rename assets/js/{5ffc8074.c7c46b28.js => 5ffc8074.f8c76d92.js} (99%) rename assets/js/{606959d6.3edb8139.js => 606959d6.68fabfee.js} (93%) rename assets/js/{60d99771.af816a56.js => 60d99771.8031c813.js} (98%) rename assets/js/{610e7425.05ef450c.js => 610e7425.235491b2.js} (98%) rename assets/js/{612b773e.977652cc.js => 612b773e.21049eaa.js} (82%) rename assets/js/{61595218.ac93c769.js => 61595218.9e0c75e2.js} (99%) rename assets/js/{617523b3.8b55c2b2.js => 617523b3.a43a650a.js} (98%) rename assets/js/{61c7d915.b1aa6299.js => 61c7d915.04828008.js} (96%) rename assets/js/{623b6c78.0172bd69.js => 623b6c78.2900a0a2.js} (85%) rename assets/js/{64536e1a.3de73596.js => 64536e1a.9ea966df.js} (98%) rename assets/js/{64947e00.fc42f77e.js => 64947e00.15f950f3.js} (99%) rename assets/js/{652c74f1.ce075dcf.js => 652c74f1.730aed76.js} (99%) rename assets/js/{65b8d1d1.0dfc869c.js => 65b8d1d1.047e76fa.js} (98%) rename assets/js/{673df5d6.49bf875e.js => 673df5d6.5d9bf0d1.js} (96%) rename assets/js/{68b7d615.f5c917b5.js => 68b7d615.fbdbb964.js} (99%) rename assets/js/{68e30702.595a5a15.js => 68e30702.7286fff2.js} (95%) rename assets/js/{69f2ab1f.7462786d.js => 69f2ab1f.a3628935.js} (98%) rename assets/js/{6a8c9872.66115f2f.js => 6a8c9872.bbc23cdd.js} (98%) rename assets/js/{6ad31330.8b092014.js => 6ad31330.6e65fbf9.js} (83%) rename assets/js/{6c124661.83050f3b.js => 6c124661.afc216c8.js} (97%) rename assets/js/{6c14a231.3eaaec7d.js => 6c14a231.2ab795f0.js} (88%) rename assets/js/{6c4340be.2f08e357.js => 6c4340be.43dfaea6.js} (99%) rename assets/js/{6c6ce37c.0aab0791.js => 6c6ce37c.8f5946ca.js} (88%) rename assets/js/{6cfcfcfb.87fe7d5d.js => 6cfcfcfb.bea63401.js} (98%) rename assets/js/{6d89025c.b6142cc1.js => 6d89025c.c42687f4.js} (99%) rename assets/js/{6efd6ec9.a781fcb6.js => 6efd6ec9.d16f9c24.js} (98%) rename assets/js/{6fe30f11.a00d78f9.js => 6fe30f11.3c83de5b.js} (85%) rename assets/js/{71a56230.f3946764.js => 71a56230.2a30f758.js} (89%) rename assets/js/{72be5fd7.8272dd94.js => 72be5fd7.847f2c01.js} (98%) rename assets/js/{72d0dc3a.74aee997.js => 72d0dc3a.19800fb7.js} (88%) rename assets/js/{741df2ae.f14df0a1.js => 741df2ae.ba6bdd66.js} (98%) rename assets/js/{74383bd8.10b03c4d.js => 74383bd8.4d536fa8.js} (96%) rename assets/js/{7515d7ec.0f02db8c.js => 7515d7ec.500009da.js} (99%) rename assets/js/{756c6ac7.d29667a5.js => 756c6ac7.55eadee8.js} (99%) rename assets/js/{75cc8326.67419917.js => 75cc8326.0b5dad68.js} (98%) rename assets/js/{766e1cc8.2071bfdd.js => 766e1cc8.83bf99b5.js} (98%) rename assets/js/{767c28af.9b3f2b00.js => 767c28af.7606b775.js} (98%) rename assets/js/{77cdcd82.17a9de0d.js => 77cdcd82.3321530b.js} (89%) rename assets/js/{7810a993.304675e2.js => 7810a993.e19c8ecc.js} (99%) rename assets/js/{78619623.de4f9ce0.js => 78619623.acbf2cff.js} (98%) rename assets/js/{78da31a1.d515a8c1.js => 78da31a1.cf24aae5.js} (99%) rename assets/js/{7afb60b1.736e3057.js => 7afb60b1.90db6183.js} (99%) rename assets/js/{7b54f5d5.10cce0ec.js => 7b54f5d5.b4c102e0.js} (99%) rename assets/js/{7bf967bc.5b329fb7.js => 7bf967bc.ad70e0f4.js} (95%) rename assets/js/{7c27e34c.f1aff3c4.js => 7c27e34c.1ac91b53.js} (95%) rename assets/js/{7e507331.2d8a1ee0.js => 7e507331.91373eaa.js} (97%) rename assets/js/{7e63a40e.32957347.js => 7e63a40e.efdb47b0.js} (96%) rename assets/js/{7ee46e43.a30e535b.js => 7ee46e43.c3a02fcd.js} (97%) create mode 100644 assets/js/8158.5d3c0904.js rename assets/js/{820db038.84479dbc.js => 820db038.30040718.js} (99%) rename assets/js/{822cd419.ae42397e.js => 822cd419.6ccd8cfe.js} (92%) rename assets/js/{82395e72.dc7ee846.js => 82395e72.4e7d40c0.js} (99%) rename assets/js/{8299d165.ebbcc1be.js => 8299d165.b0baee98.js} (93%) rename assets/js/{843ebfb4.2d883124.js => 843ebfb4.4915cff2.js} (98%) rename assets/js/{85339969.50230dc8.js => 85339969.da377a85.js} (98%) rename assets/js/{859fcda7.dd618dad.js => 859fcda7.e1a1dfa2.js} (83%) rename assets/js/{85c72337.51288902.js => 85c72337.ac0db6d4.js} (99%) rename assets/js/{87089bce.fd4ba243.js => 87089bce.44226929.js} (99%) rename assets/js/{8913b51a.b8aea398.js => 8913b51a.810e616c.js} (97%) rename assets/js/{89cae3a7.5acb06f9.js => 89cae3a7.05b5165d.js} (98%) rename assets/js/{89ed63c8.fc9f93b4.js => 89ed63c8.d51a0220.js} (94%) rename assets/js/{8b6bafea.4c53dd2a.js => 8b6bafea.69028f9b.js} (98%) rename assets/js/{8bf32d27.228eb7dc.js => 8bf32d27.3378bf47.js} (98%) rename assets/js/{8c95fc16.b5b3d6f8.js => 8c95fc16.32ab2a8a.js} (99%) rename assets/js/{8d81badd.64591638.js => 8d81badd.0c6fdf6d.js} (96%) rename assets/js/{8f7abfe1.4744fc25.js => 8f7abfe1.efb01d30.js} (98%) rename assets/js/{8f7fa040.1e12c214.js => 8f7fa040.51146fec.js} (95%) rename assets/js/{8f951ce3.38a0ed05.js => 8f951ce3.a721f857.js} (98%) rename assets/js/{8f967659.f5d72090.js => 8f967659.6ddd2ee0.js} (96%) rename assets/js/{9000b231.b513fb14.js => 9000b231.ae4fd67e.js} (88%) rename assets/js/{9073923c.5bec0ca2.js => 9073923c.d4d97409.js} (97%) rename assets/js/{90e0b7fd.b13844c0.js => 90e0b7fd.c8a731fc.js} (98%) rename assets/js/{9206a32f.5e372737.js => 9206a32f.685678c0.js} (98%) rename assets/js/{9279cea7.66cbfaa8.js => 9279cea7.f1aa6c8c.js} (99%) delete mode 100644 assets/js/9462.462ab9a2.js rename assets/js/{947f2c39.eed5acb7.js => 947f2c39.dd206cf2.js} (97%) rename assets/js/{950394a4.7223c23c.js => 950394a4.647f9c57.js} (99%) rename assets/js/{95576100.d7e099eb.js => 95576100.7cd86243.js} (73%) rename assets/js/{9664ee55.af355be4.js => 9664ee55.54f363d2.js} (99%) rename assets/js/{96877411.11d1c90c.js => 96877411.eb9ab217.js} (98%) rename assets/js/{9749ab4a.5a374ae4.js => 9749ab4a.8051e985.js} (98%) rename assets/js/{976f6afc.1e2e0c2e.js => 976f6afc.00b1895b.js} (98%) rename assets/js/{9953ecde.c796fb6a.js => 9953ecde.dc248cb7.js} (97%) rename assets/js/{9bd507da.f6812058.js => 9bd507da.92df8552.js} (89%) rename assets/js/{9d336ee4.d1d3fbf7.js => 9d336ee4.ee532efb.js} (99%) rename assets/js/{9d9f8394.57f61e6c.js => 9d9f8394.fab55070.js} (88%) rename assets/js/{9f0ecd2e.246fb3f2.js => 9f0ecd2e.ae5e64e1.js} (98%) rename assets/js/{a0bf4a5f.182ebb00.js => a0bf4a5f.74861ab2.js} (98%) rename assets/js/{a13f3cdc.e5f640b2.js => a13f3cdc.672b78da.js} (98%) rename assets/js/{a16ee953.1dff7035.js => a16ee953.9a68612f.js} (96%) rename assets/js/{a1e3d512.f4a5eedb.js => a1e3d512.9293ad8e.js} (99%) rename assets/js/{a23a5b68.c6e07c87.js => a23a5b68.b5d885a3.js} (94%) rename assets/js/{a264d631.d36b1f3c.js => a264d631.4035d270.js} (99%) rename assets/js/{a27ea030.7525b406.js => a27ea030.3bd08244.js} (92%) rename assets/js/{a27f6be0.01e05b2b.js => a27f6be0.7a85335b.js} (92%) rename assets/js/{a28aff23.d779452d.js => a28aff23.a5c896b5.js} (94%) rename assets/js/{a2d3d8d2.6d97a807.js => a2d3d8d2.02d26f89.js} (98%) rename assets/js/{a30fd8ca.cc2ee382.js => a30fd8ca.a1a62e13.js} (98%) rename assets/js/{a320b509.542f12dd.js => a320b509.90fae613.js} (98%) rename assets/js/{a3a193a6.2bb72676.js => a3a193a6.8bfca0ec.js} (92%) rename assets/js/{a55b0daf.2eec40d4.js => a55b0daf.d34ec43f.js} (99%) rename assets/js/{a9125b44.b87a4fc9.js => a9125b44.9d1e548e.js} (99%) rename assets/js/{a91c1a62.c5c73dcf.js => a91c1a62.f4487fce.js} (89%) rename assets/js/{a95c9e82.d7bf2a67.js => a95c9e82.600fb4a5.js} (98%) rename assets/js/{a99e9943.861d4b51.js => a99e9943.9c2c1474.js} (89%) rename assets/js/{a9bc4f03.250477a2.js => a9bc4f03.b3e58655.js} (98%) rename assets/js/{aa52484c.045d5777.js => aa52484c.61b177fd.js} (96%) rename assets/js/{aa5b6080.673f0a63.js => aa5b6080.381bae1e.js} (99%) rename assets/js/{aa675676.dea071fb.js => aa675676.5d9c0e5d.js} (99%) rename assets/js/{aba5bf07.b9f64b98.js => aba5bf07.da06d077.js} (99%) rename assets/js/{ac8293fa.4b2a6b5f.js => ac8293fa.33c65881.js} (88%) rename assets/js/{acbaac14.0e34867d.js => acbaac14.ce7e7bba.js} (81%) rename assets/js/{ae0a12ed.e990137c.js => ae0a12ed.108279d2.js} (81%) rename assets/js/{aebf35b6.8952f146.js => aebf35b6.7c35d9f1.js} (95%) rename assets/js/{b06c439f.ed04ab32.js => b06c439f.1814399b.js} (98%) rename assets/js/{b0ed7ea5.73400342.js => b0ed7ea5.59abd713.js} (56%) rename assets/js/{b103c05a.66f0d4d1.js => b103c05a.2c121ae6.js} (99%) rename assets/js/{b26a5b84.94499d93.js => b26a5b84.c51fb3ba.js} (99%) rename assets/js/{b2d9540a.f5cddfbb.js => b2d9540a.2a7a8831.js} (79%) rename assets/js/{b35d1284.5b3a1e1c.js => b35d1284.ce1e8749.js} (92%) rename assets/js/{b370b50c.24d426da.js => b370b50c.f9e9f1f7.js} (98%) rename assets/js/{b42f5805.f52b7c27.js => b42f5805.17436cba.js} (99%) rename assets/js/{b4657038.94094889.js => b4657038.b198b399.js} (97%) rename assets/js/{b4aea2ce.efb40527.js => b4aea2ce.310f4b7d.js} (96%) rename assets/js/{b5d0ac54.11ff1282.js => b5d0ac54.f653835d.js} (99%) rename assets/js/{b5d32d98.f0acdc5c.js => b5d32d98.bb7ca4aa.js} (96%) rename assets/js/{b6722b03.0f792d4a.js => b6722b03.74eb5003.js} (98%) rename assets/js/{b6a6a31f.59d36e4d.js => b6a6a31f.587f0446.js} (97%) rename assets/js/{b7442939.95fdc2e7.js => b7442939.8ab388f0.js} (96%) rename assets/js/{b8487569.d2bb8d8a.js => b8487569.332c9ebe.js} (99%) rename assets/js/{b94a1068.fbbebba7.js => b94a1068.29fbf944.js} (97%) rename assets/js/{b9d6d6e5.23ad4e90.js => b9d6d6e5.c8d10d7f.js} (97%) rename assets/js/{b9ea999a.a6235f86.js => b9ea999a.708e79ea.js} (98%) rename assets/js/{ba7653ad.e90cc1de.js => ba7653ad.982c6c9b.js} (89%) rename assets/js/{bb5ef1b7.82e72688.js => bb5ef1b7.80062520.js} (98%) rename assets/js/{bb9fe7c3.6af7855f.js => bb9fe7c3.6dd5e99b.js} (99%) rename assets/js/{bcb6471f.0644f520.js => bcb6471f.ecbcbd7c.js} (98%) rename assets/js/{bcc01c83.3bd62a12.js => bcc01c83.9cc13304.js} (98%) rename assets/js/{bd2c4a98.fe752412.js => bd2c4a98.cc91c3c6.js} (99%) rename assets/js/{bda39da3.60aa5ceb.js => bda39da3.da603a75.js} (98%) rename assets/js/{bdb33130.227c6eff.js => bdb33130.8f685e3c.js} (95%) rename assets/js/{be1f0304.ec3e82e5.js => be1f0304.ded0473a.js} (99%) rename assets/js/{beccb025.2c15d7a4.js => beccb025.af0246bf.js} (92%) rename assets/js/{bf2a5963.e7d07140.js => bf2a5963.c913f417.js} (98%) rename assets/js/{c007fb39.ded066fe.js => c007fb39.f54c7d5f.js} (99%) rename assets/js/{c0fa6485.d007f2cc.js => c0fa6485.2a80d401.js} (97%) rename assets/js/{c10d4a63.bd5619e8.js => c10d4a63.a576095a.js} (99%) rename assets/js/{c1fe0282.bb7327d3.js => c1fe0282.1aa8f919.js} (99%) rename assets/js/{c275698c.e2579a98.js => c275698c.48993737.js} (99%) rename assets/js/{c329487f.bef3f016.js => c329487f.4cfc14e5.js} (98%) rename assets/js/{c3701568.6e5085a4.js => c3701568.23c5e54b.js} (97%) rename assets/js/{c4d37b36.c7abd8e8.js => c4d37b36.850e299e.js} (96%) rename assets/js/{c4f5d8e4.18e9bd0f.js => c4f5d8e4.e0be6be1.js} (74%) rename assets/js/{c5fa393d.de7de634.js => c5fa393d.7442ce88.js} (98%) rename assets/js/{c69dda99.6259f383.js => c69dda99.bab571e4.js} (88%) rename assets/js/{c7a4caa1.d453236b.js => c7a4caa1.9023b5ab.js} (96%) rename assets/js/{c7e7ae18.458cd986.js => c7e7ae18.7aac8686.js} (99%) rename assets/js/{c8bdc4df.945387d4.js => c8bdc4df.f87624e5.js} (86%) rename assets/js/{c933a311.5c9464ab.js => c933a311.32f863e4.js} (99%) rename assets/js/{c953ec08.c0dba9ed.js => c953ec08.040e054c.js} (98%) rename assets/js/{ca36df4d.aeb4098b.js => ca36df4d.d8c20347.js} (99%) rename assets/js/{caa79efa.e50dc4d6.js => caa79efa.48c35a3e.js} (99%) rename assets/js/{cb01db44.4b8a5915.js => cb01db44.9d4869e3.js} (99%) rename assets/js/{cc08685c.a1c356b7.js => cc08685c.65277fd4.js} (98%) rename assets/js/{cc1c02fe.165c033e.js => cc1c02fe.61b2623b.js} (93%) rename assets/js/{cc1f18af.1fc35cb3.js => cc1f18af.2f6cfe23.js} (98%) rename assets/js/{cd25a595.2ff2dc72.js => cd25a595.ad485f50.js} (89%) rename assets/js/{cd30f404.204da6fe.js => cd30f404.69774811.js} (93%) rename assets/js/{cd699560.967c131e.js => cd699560.d49156fe.js} (93%) rename assets/js/{cddcd4e6.f9de7ce3.js => cddcd4e6.7605ffd6.js} (89%) rename assets/js/{ce95b17c.9e8c5589.js => ce95b17c.1c7cb95e.js} (96%) rename assets/js/{cf877cff.f8eace7d.js => cf877cff.13543e94.js} (84%) rename assets/js/{common.5c1c7fb8.js => common.fbe21386.js} (52%) rename assets/js/{d07ad772.9add3717.js => d07ad772.6107eae2.js} (98%) rename assets/js/{d3540d59.647d1389.js => d3540d59.3edc4833.js} (99%) rename assets/js/{d4446569.436d2399.js => d4446569.19825daa.js} (99%) rename assets/js/{d49884c9.0064b6c0.js => d49884c9.0f84318a.js} (98%) rename assets/js/{d4c8693b.e2d78398.js => d4c8693b.85c68619.js} (96%) rename assets/js/{d589d3a7.6021e5dd.js => d589d3a7.3ca3db6d.js} (82%) rename assets/js/{d6188fd4.104abbfc.js => d6188fd4.84dd5e9f.js} (89%) rename assets/js/{d6b4b60c.a2861081.js => d6b4b60c.59d29704.js} (99%) rename assets/js/{d7067606.219d83f5.js => d7067606.5ccc11ee.js} (99%) rename assets/js/{d8037f4c.8757c4fd.js => d8037f4c.7800ade4.js} (89%) rename assets/js/{d8ff000f.45d24dbc.js => d8ff000f.d78bbbdb.js} (98%) rename assets/js/{d9523c62.4313544a.js => d9523c62.56e361f4.js} (98%) rename assets/js/{db6a6f31.f295f2cd.js => db6a6f31.2ba1f92c.js} (96%) rename assets/js/{dbf2bcb3.a3e02c38.js => dbf2bcb3.7545e0e1.js} (98%) rename assets/js/{dc9a99e0.feedb86d.js => dc9a99e0.eb3363f2.js} (99%) rename assets/js/{e126d786.28afa778.js => e126d786.904fc2ce.js} (98%) rename assets/js/{e196b408.8c658db6.js => e196b408.e18e9bb2.js} (92%) rename assets/js/{e1b8bb84.e290ba7c.js => e1b8bb84.5df320fe.js} (93%) rename assets/js/{e29eb381.4559764e.js => e29eb381.ffc5e4f6.js} (94%) rename assets/js/{e2e51976.8fba7e43.js => e2e51976.c6ecb350.js} (93%) rename assets/js/{e347e63a.44f49701.js => e347e63a.2ac8076c.js} (98%) rename assets/js/{e39283a8.f1377ffd.js => e39283a8.893bcc0e.js} (96%) rename assets/js/{e45c611c.fb56a047.js => e45c611c.3998a678.js} (98%) rename assets/js/{e4c5fdc3.fb24cd67.js => e4c5fdc3.e955804e.js} (97%) rename assets/js/{e5d0e3cb.effe63e0.js => e5d0e3cb.374818b0.js} (98%) rename assets/js/{e5d7b215.1e6f5f63.js => e5d7b215.1e815ef0.js} (92%) rename assets/js/{e617c97b.d7d19342.js => e617c97b.190d4370.js} (98%) rename assets/js/{e63ebe23.e08a4a5e.js => e63ebe23.cfd28ed6.js} (99%) rename assets/js/{e6858589.96f3f1d5.js => e6858589.51ed2044.js} (92%) rename assets/js/{e688cd7e.d3b2e571.js => e688cd7e.acd459e0.js} (99%) rename assets/js/{e68b092b.fbd8b05e.js => e68b092b.91d3b084.js} (98%) rename assets/js/{e7672013.22f2c2b2.js => e7672013.9de4c4ac.js} (98%) rename assets/js/{e7ffb4b4.0ce9a2e9.js => e7ffb4b4.d76bf1af.js} (83%) rename assets/js/{e806c7bf.5077f259.js => e806c7bf.56438157.js} (97%) rename assets/js/{e91daeb9.f96f18c1.js => e91daeb9.88be2064.js} (98%) create mode 100644 assets/js/eaa287f0.30b615dd.js delete mode 100644 assets/js/eaa287f0.760001b8.js rename assets/js/{eb333c39.802adc5d.js => eb333c39.de214301.js} (98%) rename assets/js/{eca0cf35.d91731cc.js => eca0cf35.ebe74b46.js} (97%) rename assets/js/{ece9cf29.b734bc0b.js => ece9cf29.442971fe.js} (98%) rename assets/js/{ed0c0463.8c5643c1.js => ed0c0463.18d8bdce.js} (89%) rename assets/js/{eec7caa6.259ce3c5.js => eec7caa6.c4e4060d.js} (98%) rename assets/js/{f07f4757.612acd46.js => f07f4757.fcb48beb.js} (93%) rename assets/js/{f2710c27.ae6a2d96.js => f2710c27.933c6707.js} (96%) rename assets/js/{f309eabc.d9e621d8.js => f309eabc.6920a8bb.js} (98%) rename assets/js/{f48e2589.2e01819e.js => f48e2589.8017cce0.js} (83%) rename assets/js/{f4e1d1ba.b560f451.js => f4e1d1ba.9f7d01d1.js} (98%) rename assets/js/{f5b0a435.6436b061.js => f5b0a435.366e2203.js} (99%) rename assets/js/{f7c03581.b265745d.js => f7c03581.b555dacd.js} (98%) rename assets/js/{f9063551.7a9b33d9.js => f9063551.73e2d873.js} (96%) rename assets/js/{f94b062c.123eddb0.js => f94b062c.ccbd99ea.js} (81%) rename assets/js/{f9511b3d.4032d80e.js => f9511b3d.cd07b318.js} (90%) rename assets/js/{f9af357c.efe0eefa.js => f9af357c.8ec31c6c.js} (94%) rename assets/js/{fa1dd05c.e26d512a.js => fa1dd05c.f085bd72.js} (98%) rename assets/js/{fa41c0e9.4c091372.js => fa41c0e9.567f235d.js} (99%) rename assets/js/{fe153c07.5080ebdd.js => fe153c07.9070bba6.js} (98%) delete mode 100644 assets/js/main.e957a21b.js create mode 100644 assets/js/main.f2200491.js rename assets/js/{main.e957a21b.js.LICENSE.txt => main.f2200491.js.LICENSE.txt} (100%) rename assets/js/{runtime~main.4f566395.js => runtime~main.1a20f51d.js} (62%) diff --git a/404.html b/404.html index cf4ef6ff2f..d71a3ace91 100644 --- a/404.html +++ b/404.html @@ -12,13 +12,13 @@ - - + +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- - + + \ No newline at end of file diff --git a/assets/js/01fe3043.3fad64b7.js b/assets/js/01fe3043.57dca576.js similarity index 98% rename from assets/js/01fe3043.3fad64b7.js rename to assets/js/01fe3043.57dca576.js index 1808d66648..73972b03e6 100644 --- a/assets/js/01fe3043.3fad64b7.js +++ b/assets/js/01fe3043.57dca576.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2743],{20587:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"version-5.0/laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-5.0/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/5.0/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/laravel-package.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"version-5.0/docs",previous:{title:"Symfony bundle",permalink:"/docs/5.0/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/5.0/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ php artisan vendor:publish --provider=TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider\n")),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2743],{20587:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"version-5.0/laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-5.0/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/5.0/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/laravel-package.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"version-5.0/docs",previous:{title:"Symfony bundle",permalink:"/docs/5.0/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/5.0/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ php artisan vendor:publish --provider=TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider\n")),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/029c6d75.43d4c2f1.js b/assets/js/029c6d75.6e76e079.js similarity index 99% rename from assets/js/029c6d75.43d4c2f1.js rename to assets/js/029c6d75.6e76e079.js index 05793c25e8..0fcc3f9053 100644 --- a/assets/js/029c6d75.43d4c2f1.js +++ b/assets/js/029c6d75.6e76e079.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6599],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},14829:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-4.2/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.2/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/4.2/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/symfony-bundle-advanced.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"version-4.2/docs",previous:{title:"Class with multiple output types",permalink:"/docs/4.2/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/4.2/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6599],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},14829:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-4.2/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.2/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/4.2/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/symfony-bundle-advanced.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"version-4.2/docs",previous:{title:"Class with multiple output types",permalink:"/docs/4.2/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/4.2/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/02c5a8b1.35dbaf12.js b/assets/js/02c5a8b1.ca45c617.js similarity index 98% rename from assets/js/02c5a8b1.35dbaf12.js rename to assets/js/02c5a8b1.ca45c617.js index 6f65b09c61..390c99dde8 100644 --- a/assets/js/02c5a8b1.35dbaf12.js +++ b/assets/js/02c5a8b1.ca45c617.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1289],{80157:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-6.1/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.1/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/6.1/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/symfony-bundle.md",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"docs",previous:{title:"Getting Started",permalink:"/docs/6.1/getting-started"},next:{title:"Laravel package",permalink:"/docs/6.1/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-6.1/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.1/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/6.1/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/symfony-bundle.md",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"docs",previous:{title:"Getting Started",permalink:"/docs/6.1/getting-started"},next:{title:"Laravel package",permalink:"/docs/6.1/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>p,frontMatter:()=>a,metadata:()=>s,toc:()=>c});var i=n(58168),r=(n(96540),n(15680));n(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework",original_id:"implementing-security"},o=void 0,s={unversionedId:"implementing-security",id:"version-4.0/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package",source:"@site/versioned_docs/version-4.0/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/4.0/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/implementing-security.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework",original_id:"implementing-security"},sidebar:"version-4.0/docs",previous:{title:"Fine grained security",permalink:"/docs/4.0/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/4.0/query-plan"}},u={},c=[],l={toc:c},g="wrapper";function p(e){let{components:t,...n}=e;return(0,r.yg)(g,(0,i.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package"),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.0/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3765],{36664:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>p,frontMatter:()=>a,metadata:()=>s,toc:()=>c});var n=i(58168),r=(i(96540),i(15680));i(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework",original_id:"implementing-security"},o=void 0,s={unversionedId:"implementing-security",id:"version-4.0/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package",source:"@site/versioned_docs/version-4.0/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/4.0/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/implementing-security.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework",original_id:"implementing-security"},sidebar:"version-4.0/docs",previous:{title:"Fine grained security",permalink:"/docs/4.0/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/4.0/query-plan"}},u={},c=[],l={toc:c},g="wrapper";function p(e){let{components:t,...i}=e;return(0,r.yg)(g,(0,n.A)({},l,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package"),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.0/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0370fbfb.5a5b55ad.js b/assets/js/0370fbfb.b873a3e9.js similarity index 83% rename from assets/js/0370fbfb.5a5b55ad.js rename to assets/js/0370fbfb.b873a3e9.js index 601a4ae7b8..d8ea7b75e8 100644 --- a/assets/js/0370fbfb.5a5b55ad.js +++ b/assets/js/0370fbfb.b873a3e9.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9793],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var r=a(96540),t=a(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>$});var r=a(58168),t=a(96540),o=a(20053),i=a(23104),l=a(56347),s=a(57485),c=a(31682),u=a(89466);function p(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:r,default:t}}=e;return{value:n,label:a,attributes:r,default:t}}))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??p(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function h(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:a}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,c]=m({queryString:a,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,u.Dv)(a);return[r,(0,t.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:r}),g=(()=>{const e=s??p;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),c(e),y(e)}),[c,y,o]),tabValues:o}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:s,tabValues:c}=e;const u=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),d=e=>{const n=e.currentTarget,a=u.indexOf(n),r=c[a].value;r!==l&&(p(n),s(r))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=u.indexOf(e.currentTarget)+1;n=u[a]??u[0];break}case"ArrowLeft":{const a=u.indexOf(e.currentTarget)-1;n=u[a]??u[u.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return t.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>u.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),a??n)})))}function w(e){let{lazy:n,children:a,selectedValue:r}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,r.A)({},e,n)),t.createElement(w,(0,r.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,r.A)({key:String(n)},e))}},79111:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>d,frontMatter:()=>o,metadata:()=>l,toc:()=>c});var r=a(58168),t=(a(96540),a(15680));a(67443),a(11470),a(19365);const o={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},i=void 0,l={unversionedId:"other-frameworks",id:"version-6.1/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-6.1/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/6.1/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/other-frameworks.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"docs",previous:{title:"Universal service providers",permalink:"/docs/6.1/universal-service-providers"},next:{title:"Queries",permalink:"/docs/6.1/queries"}},s={},c=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],u={toc:c},p="wrapper";function d(e){let{components:n,...a}=e;return(0,t.yg)(p,(0,r.A)({},u,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Set a default InputType validator service to handle validation on all `Input` annotated types\n$factory->setInputTypeValidator($validator);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"$builder->setUrl(\"/graphql\"); // Modify the URL endpoint (defaults to /graphql)\n\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object.\n// Define your own formatter and error handlers for Webonyx.\n$config->setErrorFormatter([ExceptionHandler::class, 'errorFormatter']);\n$config->setErrorsHandler([ExceptionHandler::class, 'errorHandler']);\n\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n")),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new LazyContainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9793],{19365:(e,n,r)=>{r.d(n,{A:()=>i});var a=r(96540),t=r(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:r,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:r},n)}},11470:(e,n,r)=>{r.d(n,{A:()=>$});var a=r(58168),t=r(96540),o=r(20053),i=r(23104),l=r(56347),s=r(57485),c=r(31682),u=r(89466);function p(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:r,attributes:a,default:t}}=e;return{value:n,label:r,attributes:a,default:t}}))}function d(e){const{values:n,children:r}=e;return(0,t.useMemo)((()=>{const e=n??p(r);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,r])}function h(e){let{value:n,tabValues:r}=e;return r.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:r}=e;const a=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:r}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!r)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return r??null}({queryString:n,groupId:r});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(a.location.search);n.set(o,e),a.replace({...a.location,search:n.toString()})}),[o,a])]}function y(e){const{defaultValue:n,queryString:r=!1,groupId:a}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:r}=e;if(0===r.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:r}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${r.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=r.find((e=>e.default))??r[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:o}))),[s,c]=m({queryString:r,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const r=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,o]=(0,u.Dv)(r);return[a,(0,t.useCallback)((e=>{r&&o.set(e)}),[r,o])]}({groupId:a}),g=(()=>{const e=s??p;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),c(e),y(e)}),[c,y,o]),tabValues:o}}var g=r(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:r,selectedValue:l,selectValue:s,tabValues:c}=e;const u=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),d=e=>{const n=e.currentTarget,r=u.indexOf(n),a=c[r].value;a!==l&&(p(n),s(a))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const r=u.indexOf(e.currentTarget)+1;n=u[r]??u[0];break}case"ArrowLeft":{const r=u.indexOf(e.currentTarget)-1;n=u[r]??u[u.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":r},n)},c.map((e=>{let{value:n,label:r,attributes:i}=e;return t.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>u.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),r??n)})))}function w(e){let{lazy:n,children:r,selectedValue:a}=e;const o=(Array.isArray(r)?r:[r]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===a));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,a.A)({},e,n)),t.createElement(w,(0,a.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,a.A)({key:String(n)},e))}},79111:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>d,frontMatter:()=>o,metadata:()=>l,toc:()=>c});var a=r(58168),t=(r(96540),r(15680));r(67443),r(11470),r(19365);const o={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},i=void 0,l={unversionedId:"other-frameworks",id:"version-6.1/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-6.1/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/6.1/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/other-frameworks.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"docs",previous:{title:"Universal service providers",permalink:"/docs/6.1/universal-service-providers"},next:{title:"Queries",permalink:"/docs/6.1/queries"}},s={},c=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],u={toc:c},p="wrapper";function d(e){let{components:n,...r}=e;return(0,t.yg)(p,(0,a.A)({},u,r,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Set a default InputType validator service to handle validation on all `Input` annotated types\n$factory->setInputTypeValidator($validator);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"$builder->setUrl(\"/graphql\"); // Modify the URL endpoint (defaults to /graphql)\n\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object.\n// Define your own formatter and error handlers for Webonyx.\n$config->setErrorFormatter([ExceptionHandler::class, 'errorFormatter']);\n$config->setErrorsHandler([ExceptionHandler::class, 'errorHandler']);\n\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n")),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new LazyContainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/03abab96.4f809df3.js b/assets/js/03abab96.1c428084.js similarity index 96% rename from assets/js/03abab96.4f809df3.js rename to assets/js/03abab96.1c428084.js index 93bc1b5e23..c9bb6b54fe 100644 --- a/assets/js/03abab96.4f809df3.js +++ b/assets/js/03abab96.1c428084.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4021],{83695:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>p,frontMatter:()=>o,metadata:()=>s,toc:()=>u});var n=i(58168),a=(i(96540),i(15680));i(67443);const o={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},r=void 0,s={unversionedId:"autowiring",id:"autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/docs/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/next/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/autowiring.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"docs",previous:{title:"Type mapping",permalink:"/docs/next/type-mapping"},next:{title:"Extending a type",permalink:"/docs/next/extend-type"}},l={},u=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],c={toc:u},d="wrapper";function p(e){let{components:t,...i}=e;return(0,a.yg)(d,(0,n.A)({},c,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,a.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,a.yg)("p",null,"Most of the time, your ",(0,a.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,a.yg)("h2",{id:"sample"},"Sample"),(0,a.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,a.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")),(0,a.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,a.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,a.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,a.yg)("h2",{id:"best-practices"},"Best practices"),(0,a.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,a.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,a.yg)("pre",null,(0,a.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,a.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,a.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,a.yg)("pre",null,(0,a.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,a.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,a.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,a.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n')),(0,a.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,a.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,a.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,a.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,a.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,a.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,a.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,a.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4021],{83695:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>p,frontMatter:()=>o,metadata:()=>s,toc:()=>u});var n=i(58168),a=(i(96540),i(15680));i(67443);const o={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},r=void 0,s={unversionedId:"autowiring",id:"autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/docs/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/next/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/autowiring.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"docs",previous:{title:"Type mapping",permalink:"/docs/next/type-mapping"},next:{title:"Extending a type",permalink:"/docs/next/extend-type"}},l={},u=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],c={toc:u},d="wrapper";function p(e){let{components:t,...i}=e;return(0,a.yg)(d,(0,n.A)({},c,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,a.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,a.yg)("p",null,"Most of the time, your ",(0,a.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,a.yg)("h2",{id:"sample"},"Sample"),(0,a.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,a.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")),(0,a.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,a.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,a.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,a.yg)("h2",{id:"best-practices"},"Best practices"),(0,a.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,a.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,a.yg)("pre",null,(0,a.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,a.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,a.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,a.yg)("pre",null,(0,a.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,a.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,a.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,a.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n')),(0,a.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,a.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,a.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,a.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,a.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,a.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,a.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,a.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/03c886f6.b5776fa5.js b/assets/js/03c886f6.b2d0817c.js similarity index 96% rename from assets/js/03c886f6.b5776fa5.js rename to assets/js/03c886f6.b2d0817c.js index d6f6f467b5..b175d2b79c 100644 --- a/assets/js/03c886f6.b5776fa5.js +++ b/assets/js/03c886f6.b2d0817c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4827],{95454:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>g,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var t=n(58168),r=(n(96540),n(15680));n(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-7.0.0/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-7.0.0/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/argument-resolving.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"docs",previous:{title:"Custom annotations",permalink:"/docs/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/extend-input-type"}},s={},p=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},d="wrapper";function g(e){let{components:a,...n}=e;return(0,r.yg)(d,(0,t.A)({},m,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Autowire")," annotation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4827],{95454:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var t=n(58168),r=(n(96540),n(15680));n(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-7.0.0/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-7.0.0/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/argument-resolving.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"docs",previous:{title:"Custom annotations",permalink:"/docs/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/extend-input-type"}},s={},p=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},g="wrapper";function d(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},m,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Autowire")," annotation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/05e8cfc0.14924a11.js b/assets/js/05e8cfc0.7b22ffae.js similarity index 92% rename from assets/js/05e8cfc0.14924a11.js rename to assets/js/05e8cfc0.7b22ffae.js index e3829e17a6..123e623963 100644 --- a/assets/js/05e8cfc0.14924a11.js +++ b/assets/js/05e8cfc0.7b22ffae.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5091],{3716:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>l,default:()=>y,frontMatter:()=>o,metadata:()=>i,toc:()=>s});var a=t(58168),r=(t(96540),t(15680));t(67443);const o={id:"query-plan",title:"Query plan",sidebar_label:"Query plan",original_id:"query-plan"},l=void 0,i={unversionedId:"query-plan",id:"version-4.0/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-4.0/query_plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/4.0/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/query_plan.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan",original_id:"query-plan"},sidebar:"version-4.0/docs",previous:{title:"Connecting security to your framework",permalink:"/docs/4.0/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/4.0/prefetch-method"}},u={},s=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],p={toc:s},d="wrapper";function y(e){let{components:n,...t}=e;return(0,r.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5091],{3716:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>o,default:()=>y,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var a=t(58168),r=(t(96540),t(15680));t(67443);const l={id:"query-plan",title:"Query plan",sidebar_label:"Query plan",original_id:"query-plan"},o=void 0,i={unversionedId:"query-plan",id:"version-4.0/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-4.0/query_plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/4.0/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/query_plan.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan",original_id:"query-plan"},sidebar:"version-4.0/docs",previous:{title:"Connecting security to your framework",permalink:"/docs/4.0/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/4.0/prefetch-method"}},u={},s=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],p={toc:s},d="wrapper";function y(e){let{components:n,...t}=e;return(0,r.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/05fed6b1.5b1f7b07.js b/assets/js/05fed6b1.50b5309d.js similarity index 73% rename from assets/js/05fed6b1.5b1f7b07.js rename to assets/js/05fed6b1.50b5309d.js index b0d914aa78..dfb35a38fc 100644 --- a/assets/js/05fed6b1.5b1f7b07.js +++ b/assets/js/05fed6b1.50b5309d.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4620],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),r=n(96540),i=n(20053),o=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function p(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:n,groupId:a}),[d,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(d(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),n??t)})))}function b(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,a.A)({},e,t)),r.createElement(b,(0,a.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},24391:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var a=n(58168),r=(n(96540),n(15680)),i=(n(67443),n(11470)),o=n(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-4.2/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-4.2/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/4.2/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/autowiring.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"version-4.2/docs",previous:{title:"Type mapping",permalink:"/docs/4.2/type-mapping"},next:{title:"Extending a type",permalink:"/docs/4.2/extend-type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4620],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),i=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(n.location.search);t.set(i,e),n.replace({...n.location,search:t.toString()})}),[i,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,i]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:n}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,t)),r.createElement(b,(0,n.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},24391:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),i=(a(67443),a(11470)),o=a(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-4.2/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-4.2/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/4.2/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/autowiring.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"version-4.2/docs",previous:{title:"Type mapping",permalink:"/docs/4.2/type-mapping"},next:{title:"Extending a type",permalink:"/docs/4.2/extend-type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(h,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/06c02cc7.5a703743.js b/assets/js/06c02cc7.8457a813.js similarity index 92% rename from assets/js/06c02cc7.5a703743.js rename to assets/js/06c02cc7.8457a813.js index 9825cfcdd8..864c0a2fb1 100644 --- a/assets/js/06c02cc7.5a703743.js +++ b/assets/js/06c02cc7.8457a813.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4803],{75959:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-7.0.0/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-7.0.0/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/universal-service-providers.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"docs",previous:{title:"Laravel package",permalink:"/docs/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4803],{75959:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>p});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-7.0.0/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-7.0.0/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/universal-service-providers.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"docs",previous:{title:"Laravel package",permalink:"/docs/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/other-frameworks"}},l={},p=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:p},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/07623f9a.e42afba5.js b/assets/js/07623f9a.3b28ef48.js similarity index 98% rename from assets/js/07623f9a.e42afba5.js rename to assets/js/07623f9a.3b28ef48.js index 4455622873..916e152d2e 100644 --- a/assets/js/07623f9a.e42afba5.js +++ b/assets/js/07623f9a.3b28ef48.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6925],{42562:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var a=t(58168),r=(t(96540),t(15680));t(67443);const l={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},o=void 0,i={unversionedId:"symfony-bundle",id:"version-4.0/symfony-bundle",title:"Getting started with Symfony",description:"The GraphQLite bundle is compatible with Symfony 4.x and Symfony 5.x.",source:"@site/versioned_docs/version-4.0/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/4.0/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/symfony-bundle.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},sidebar:"version-4.0/docs",previous:{title:"Getting Started",permalink:"/docs/4.0/getting-started"},next:{title:"Laravel package",permalink:"/docs/4.0/laravel-package"}},p={},s=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],y={toc:s},g="wrapper";function d(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},y,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,r.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,r.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,r.yg)("p",null,"Now, go to the ",(0,r.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"config/packages/graphqlite.yaml")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,r.yg)("p",null,"More advanced parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,r.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,r.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,r.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"app/AppKernel.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var a=t(58168),r=(t(96540),t(15680));t(67443);const l={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},o=void 0,i={unversionedId:"symfony-bundle",id:"version-4.0/symfony-bundle",title:"Getting started with Symfony",description:"The GraphQLite bundle is compatible with Symfony 4.x and Symfony 5.x.",source:"@site/versioned_docs/version-4.0/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/4.0/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/symfony-bundle.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},sidebar:"version-4.0/docs",previous:{title:"Getting Started",permalink:"/docs/4.0/getting-started"},next:{title:"Laravel package",permalink:"/docs/4.0/laravel-package"}},p={},s=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],y={toc:s},g="wrapper";function d(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},y,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,r.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,r.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,r.yg)("p",null,"Now, go to the ",(0,r.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"config/packages/graphqlite.yaml")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,r.yg)("p",null,"More advanced parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,r.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,r.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,r.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"app/AppKernel.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>s,metadata:()=>i,toc:()=>h});var r=n(58168),a=(n(96540),n(15680));n(67443);const s={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records",original_id:"prefetch-method"},o=void 0,i={unversionedId:"prefetch-method",id:"version-4.0/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-4.0/prefetch_method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/4.0/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/prefetch_method.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records",original_id:"prefetch-method"},sidebar:"version-4.0/docs",previous:{title:"Query plan",permalink:"/docs/4.0/query-plan"},next:{title:"File uploads",permalink:"/docs/4.0/file-uploads"}},l={},h=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],p={toc:h},d="wrapper";function c(e){let{components:t,...n}=e;return(0,a.yg)(d,(0,r.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("h2",{id:"the-problem"},"The problem"),(0,a.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,a.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,a.yg)("p",null,"A naive implementation will do this:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,a.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,a.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,a.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,a.yg)("a",{parentName:"p",href:"/docs/4.0/query-plan"},'"analyzing the query plan" documentation'),"."),(0,a.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,a.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,a.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')),(0,a.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,a.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,a.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,a.yg)("p",null,"For instance:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')),(0,a.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2174],{69463:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>s,metadata:()=>i,toc:()=>h});var r=n(58168),a=(n(96540),n(15680));n(67443);const s={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records",original_id:"prefetch-method"},o=void 0,i={unversionedId:"prefetch-method",id:"version-4.0/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-4.0/prefetch_method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/4.0/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/prefetch_method.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records",original_id:"prefetch-method"},sidebar:"version-4.0/docs",previous:{title:"Query plan",permalink:"/docs/4.0/query-plan"},next:{title:"File uploads",permalink:"/docs/4.0/file-uploads"}},l={},h=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],p={toc:h},d="wrapper";function c(e){let{components:t,...n}=e;return(0,a.yg)(d,(0,r.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("h2",{id:"the-problem"},"The problem"),(0,a.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,a.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,a.yg)("p",null,"A naive implementation will do this:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,a.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,a.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,a.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,a.yg)("a",{parentName:"p",href:"/docs/4.0/query-plan"},'"analyzing the query plan" documentation'),"."),(0,a.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,a.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,a.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')),(0,a.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,a.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,a.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,a.yg)("p",null,"For instance:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')),(0,a.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/077a13b8.95292adb.js b/assets/js/077a13b8.3f86c654.js similarity index 98% rename from assets/js/077a13b8.95292adb.js rename to assets/js/077a13b8.3f86c654.js index bb557b7ee5..41226310e4 100644 --- a/assets/js/077a13b8.95292adb.js +++ b/assets/js/077a13b8.3f86c654.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6986],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},24933:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},u=void 0,s={unversionedId:"extend-input-type",id:"version-5.0/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-5.0/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/5.0/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/extend-input-type.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"version-5.0/docs",previous:{title:"Custom argument resolving",permalink:"/docs/5.0/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/5.0/multiple-output-types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6986],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},24933:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},u=void 0,s={unversionedId:"extend-input-type",id:"version-5.0/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-5.0/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/5.0/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/extend-input-type.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"version-5.0/docs",previous:{title:"Custom argument resolving",permalink:"/docs/5.0/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/5.0/multiple-output-types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/079b0d3e.b634668e.js b/assets/js/079b0d3e.6516370a.js similarity index 99% rename from assets/js/079b0d3e.b634668e.js rename to assets/js/079b0d3e.6516370a.js index 29c87a1918..a56d4cc1f6 100644 --- a/assets/js/079b0d3e.b634668e.js +++ b/assets/js/079b0d3e.6516370a.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2369],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),o=t(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),o=t(96540),i=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:o}}=e;return{value:n,label:t,attributes:a,default:o}}))}function d(e){const{values:n,children:t}=e;return(0,o.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,o.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,o.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,n)),o.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return o.createElement(v,(0,a.A)({key:String(n)},e))}},91271:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),o=(t(96540),t(15680)),i=(t(67443),t(11470)),r=t(19365);const l={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},u=void 0,s={unversionedId:"external-type-declaration",id:"version-6.0/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-6.0/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/6.0/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/external-type-declaration.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"docs",previous:{title:"Extending a type",permalink:"/docs/6.0/extend-type"},next:{title:"Input types",permalink:"/docs/6.0/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,o.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),").\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object.\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/6.0/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/6.0/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2369],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),o=t(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),o=t(96540),i=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:o}}=e;return{value:n,label:t,attributes:a,default:o}}))}function d(e){const{values:n,children:t}=e;return(0,o.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,o.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,o.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,n)),o.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return o.createElement(v,(0,a.A)({key:String(n)},e))}},91271:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),o=(t(96540),t(15680)),i=(t(67443),t(11470)),r=t(19365);const l={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},u=void 0,s={unversionedId:"external-type-declaration",id:"version-6.0/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-6.0/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/6.0/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/external-type-declaration.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"docs",previous:{title:"Extending a type",permalink:"/docs/6.0/extend-type"},next:{title:"Input types",permalink:"/docs/6.0/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,o.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),").\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object.\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/6.0/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/6.0/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/07c49ebd.cbc22070.js b/assets/js/07c49ebd.f683e66a.js similarity index 94% rename from assets/js/07c49ebd.cbc22070.js rename to assets/js/07c49ebd.f683e66a.js index 06a3abdbee..5f54318c3b 100644 --- a/assets/js/07c49ebd.cbc22070.js +++ b/assets/js/07c49ebd.f683e66a.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8633],{37721:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var i=t(58168),a=(t(96540),t(15680));t(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations",original_id:"field-middlewares"},r=void 0,o={unversionedId:"field-middlewares",id:"version-4.1/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.1/field_middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/4.1/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/field_middlewares.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations",original_id:"field-middlewares"},sidebar:"version-4.1/docs",previous:{title:"Custom types",permalink:"/docs/4.1/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/4.1/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour\nof a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:t(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library (for PHP 7+) and/or by PHP 8 attributes."),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"OnlyDebug.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface\nis a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation\nis to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:",(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"li"},"graphql.field_middleware")," tag.")))}c.isMDXComponent=!0},8643:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8633],{37721:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var t=i(58168),a=(i(96540),i(15680));i(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations",original_id:"field-middlewares"},r=void 0,o={unversionedId:"field-middlewares",id:"version-4.1/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.1/field_middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/4.1/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/field_middlewares.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations",original_id:"field-middlewares"},sidebar:"version-4.1/docs",previous:{title:"Custom types",permalink:"/docs/4.1/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/4.1/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,t.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour\nof a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:i(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library (for PHP 7+) and/or by PHP 8 attributes."),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"OnlyDebug.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface\nis a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation\nis to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:",(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"li"},"graphql.field_middleware")," tag.")))}c.isMDXComponent=!0},8643:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file diff --git a/assets/js/085c135f.dccfc8aa.js b/assets/js/085c135f.a8d57bf6.js similarity index 88% rename from assets/js/085c135f.dccfc8aa.js rename to assets/js/085c135f.a8d57bf6.js index 1243f7b52f..63beeab793 100644 --- a/assets/js/085c135f.dccfc8aa.js +++ b/assets/js/085c135f.a8d57bf6.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[362],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=p(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),b=(()=>{const e=s??d;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==o&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,l.A)("tabs__item",y.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(f,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function T(e){const t=(0,b.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},2993:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},s=void 0,i={unversionedId:"index",id:"version-4.3/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-4.3/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/4.3/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/README.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"version-4.3/docs",next:{title:"Getting Started",permalink:"/docs/4.3/getting-started"}},c={},d=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],p={toc:d},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[362],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),b=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==o&&(p(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,l.A)("tabs__item",y.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(f,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function T(e){const t=(0,b.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},2993:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},s=void 0,i={unversionedId:"index",id:"version-4.3/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-4.3/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/4.3/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/README.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"version-4.3/docs",next:{title:"Getting Started",permalink:"/docs/4.3/getting-started"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:p},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/08fe23a4.fd364b45.js b/assets/js/08fe23a4.be9b2800.js similarity index 83% rename from assets/js/08fe23a4.fd364b45.js rename to assets/js/08fe23a4.be9b2800.js index a342fc62b9..776cdd07c3 100644 --- a/assets/js/08fe23a4.fd364b45.js +++ b/assets/js/08fe23a4.be9b2800.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[514],{65235:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>u,contentTitle:()=>s,default:()=>c,frontMatter:()=>o,metadata:()=>r,toc:()=>d});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,r={unversionedId:"mutations",id:"version-6.1/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-6.1/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/6.1/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/mutations.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"docs",previous:{title:"Queries",permalink:"/docs/6.1/queries"},next:{title:"Type mapping",permalink:"/docs/6.1/type-mapping"}},u={},d=[],p={toc:d},l="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(l,(0,a.A)({},p,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In GraphQLite, mutations are created ",(0,i.yg)("a",{parentName:"p",href:"/docs/6.1/queries"},"like queries"),"."),(0,i.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[514],{65235:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>u,contentTitle:()=>s,default:()=>c,frontMatter:()=>o,metadata:()=>r,toc:()=>l});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,r={unversionedId:"mutations",id:"version-6.1/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-6.1/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/6.1/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/mutations.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"docs",previous:{title:"Queries",permalink:"/docs/6.1/queries"},next:{title:"Type mapping",permalink:"/docs/6.1/type-mapping"}},u={},l=[],d={toc:l},p="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(p,(0,a.A)({},d,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In GraphQLite, mutations are created ",(0,i.yg)("a",{parentName:"p",href:"/docs/6.1/queries"},"like queries"),"."),(0,i.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0a57d896.019ac7d9.js b/assets/js/0a57d896.e912e3ff.js similarity index 99% rename from assets/js/0a57d896.019ac7d9.js rename to assets/js/0a57d896.e912e3ff.js index a33f1e7f10..3b80d37526 100644 --- a/assets/js/0a57d896.019ac7d9.js +++ b/assets/js/0a57d896.e912e3ff.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7471],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},18419:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling",original_id:"error-handling"},s=void 0,u={unversionedId:"error-handling",id:"version-4.1/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-4.1/error_handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/4.1/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/error_handling.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling",original_id:"error-handling"},sidebar:"version-4.1/docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/4.1/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/4.1/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/4.1/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/4.1/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7471],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},18419:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling",original_id:"error-handling"},s=void 0,u={unversionedId:"error-handling",id:"version-4.1/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-4.1/error_handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/4.1/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/error_handling.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling",original_id:"error-handling"},sidebar:"version-4.1/docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/4.1/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/4.1/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/4.1/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/4.1/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0cb7e976.b7d4f44b.js b/assets/js/0cb7e976.2f6d64b5.js similarity index 98% rename from assets/js/0cb7e976.b7d4f44b.js rename to assets/js/0cb7e976.2f6d64b5.js index d9bb082bbf..206fd0f230 100644 --- a/assets/js/0cb7e976.b7d4f44b.js +++ b/assets/js/0cb7e976.2f6d64b5.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8112],{17678:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>p,frontMatter:()=>r,metadata:()=>s,toc:()=>l});var a=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},o=void 0,s={unversionedId:"fine-grained-security",id:"fine-grained-security",title:"Fine grained security",description:"If the #[Logged] and #[Right] attributes are not",source:"@site/docs/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/next/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/fine-grained-security.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},sidebar:"docs",previous:{title:"Authentication and authorization",permalink:"/docs/next/authentication-authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/next/implementing-security"}},u={},l=[{value:"Using the #Security attribute",id:"using-the-security-attribute",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],c={toc:l},g="wrapper";function p(e){let{components:t,...n}=e;return(0,i.yg)(g,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"If the ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/authentication-authorization#logged-and-right-annotations"},(0,i.yg)("inlineCode",{parentName:"a"},"#[Logged]")," and ",(0,i.yg)("inlineCode",{parentName:"a"},"#[Right]")," attributes")," are not\ngranular enough for your needs, you can use the advanced ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute."),(0,i.yg)("p",null,"Using the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute, you can write an ",(0,i.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,i.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,i.yg)("li",{parentName:"ul"},"...")),(0,i.yg)("h2",{id:"using-the-security-attribute"},"Using the #","[Security]"," attribute"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute must conform to ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,i.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,i.yg)("code",null,"#[Security]")," attribute. Most of the inspiration of this attribute comes from Symfony. Warning though! GraphQLite's ",(0,i.yg)("code",null,"#[Security]")," attribute and Symfony's ",(0,i.yg)("code",null,"#[Security]")," attribute are slightly different. Especially, the two attributes do not live in the same namespace!"),(0,i.yg)("h2",{id:"the-is_granted-function"},"The ",(0,i.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n")),(0,i.yg)("p",null,"is similar to"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n')),(0,i.yg)("p",null,"In addition, the ",(0,i.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n")),(0,i.yg)("p",null,"In the example above, the ",(0,i.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,i.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,i.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,i.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,i.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," expression."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')),(0,i.yg)("p",null,"In the example above, we tweak a bit the Security attribute purpose to do simple input validation."),(0,i.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,i.yg)("p",null,"You can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n')),(0,i.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,i.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,i.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/authentication-authorization#not-throwing-errors"},(0,i.yg)("inlineCode",{parentName:"a"},"#[FailWith]")," attribute"),"\nbut for a given ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute."),(0,i.yg)("p",null,"You cannot use the ",(0,i.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,i.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,i.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,i.yg)("p",null,"You can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n')),(0,i.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,i.yg)("p",null,"You can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')),(0,i.yg)("h2",{id:"available-scope"},"Available scope"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute can be used in any query, mutation or field, so anywhere you have a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Mutation]"),"\nor ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute."),(0,i.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n")),(0,i.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,i.yg)("p",null,"If you are using Symfony, you will ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,i.yg)("p",null,"If you are using Laravel, you will ",(0,i.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,i.yg)("p",null,"If you are using another framework, you need to know that the ",(0,i.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,i.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,i.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8112],{17678:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>p,frontMatter:()=>r,metadata:()=>s,toc:()=>l});var a=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},o=void 0,s={unversionedId:"fine-grained-security",id:"fine-grained-security",title:"Fine grained security",description:"If the #[Logged] and #[Right] attributes are not",source:"@site/docs/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/next/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/fine-grained-security.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},sidebar:"docs",previous:{title:"Authentication and authorization",permalink:"/docs/next/authentication-authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/next/implementing-security"}},u={},l=[{value:"Using the #Security attribute",id:"using-the-security-attribute",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],c={toc:l},g="wrapper";function p(e){let{components:t,...n}=e;return(0,i.yg)(g,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"If the ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/authentication-authorization#logged-and-right-annotations"},(0,i.yg)("inlineCode",{parentName:"a"},"#[Logged]")," and ",(0,i.yg)("inlineCode",{parentName:"a"},"#[Right]")," attributes")," are not\ngranular enough for your needs, you can use the advanced ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute."),(0,i.yg)("p",null,"Using the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute, you can write an ",(0,i.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,i.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,i.yg)("li",{parentName:"ul"},"...")),(0,i.yg)("h2",{id:"using-the-security-attribute"},"Using the #","[Security]"," attribute"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute must conform to ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,i.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,i.yg)("code",null,"#[Security]")," attribute. Most of the inspiration of this attribute comes from Symfony. Warning though! GraphQLite's ",(0,i.yg)("code",null,"#[Security]")," attribute and Symfony's ",(0,i.yg)("code",null,"#[Security]")," attribute are slightly different. Especially, the two attributes do not live in the same namespace!"),(0,i.yg)("h2",{id:"the-is_granted-function"},"The ",(0,i.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n")),(0,i.yg)("p",null,"is similar to"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n')),(0,i.yg)("p",null,"In addition, the ",(0,i.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n")),(0,i.yg)("p",null,"In the example above, the ",(0,i.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,i.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,i.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,i.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,i.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," expression."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')),(0,i.yg)("p",null,"In the example above, we tweak a bit the Security attribute purpose to do simple input validation."),(0,i.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,i.yg)("p",null,"You can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n')),(0,i.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,i.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,i.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/authentication-authorization#not-throwing-errors"},(0,i.yg)("inlineCode",{parentName:"a"},"#[FailWith]")," attribute"),"\nbut for a given ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute."),(0,i.yg)("p",null,"You cannot use the ",(0,i.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,i.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,i.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,i.yg)("p",null,"You can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n')),(0,i.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,i.yg)("p",null,"You can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')),(0,i.yg)("h2",{id:"available-scope"},"Available scope"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute can be used in any query, mutation or field, so anywhere you have a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Mutation]"),"\nor ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute."),(0,i.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n")),(0,i.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,i.yg)("p",null,"If you are using Symfony, you will ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,i.yg)("p",null,"If you are using Laravel, you will ",(0,i.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,i.yg)("p",null,"If you are using another framework, you need to know that the ",(0,i.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,i.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,i.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0d7bb119.295445f4.js b/assets/js/0d7bb119.c337f7c6.js similarity index 98% rename from assets/js/0d7bb119.295445f4.js rename to assets/js/0d7bb119.c337f7c6.js index 02bb8cce55..bacf757a67 100644 --- a/assets/js/0d7bb119.295445f4.js +++ b/assets/js/0d7bb119.c337f7c6.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5981],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),u=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==u&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",b.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},8532:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>u,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const u={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-4.2/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-4.2/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/4.2/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/query-plan.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"version-4.2/docs",previous:{title:"Connecting security to your framework",permalink:"/docs/4.2/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/4.2/prefetch-method"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5981],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),u=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==u&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",b.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},8532:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>u,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const u={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-4.2/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-4.2/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/4.2/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/query-plan.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"version-4.2/docs",previous:{title:"Connecting security to your framework",permalink:"/docs/4.2/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/4.2/prefetch-method"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0db009bb.2dc8137c.js b/assets/js/0db009bb.5e17f885.js similarity index 83% rename from assets/js/0db009bb.2dc8137c.js rename to assets/js/0db009bb.5e17f885.js index cf54d9356d..605ffd7bb7 100644 --- a/assets/js/0db009bb.2dc8137c.js +++ b/assets/js/0db009bb.5e17f885.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8028],{9:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>c,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},o=void 0,s={unversionedId:"getting-started",id:"version-4.3/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-4.3/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/4.3/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/getting-started.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},sidebar:"version-4.3/docs",previous:{title:"GraphQLite",permalink:"/docs/4.3/"},next:{title:"Symfony bundle",permalink:"/docs/4.3/symfony-bundle"}},d={},l=[],p={toc:l},g="wrapper";function c(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,r.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/universal-service-providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/other-frameworks"},"Get started with another framework (or no framework)"))))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8028],{9:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>p,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},o=void 0,s={unversionedId:"getting-started",id:"version-4.3/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-4.3/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/4.3/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/getting-started.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},sidebar:"version-4.3/docs",previous:{title:"GraphQLite",permalink:"/docs/4.3/"},next:{title:"Symfony bundle",permalink:"/docs/4.3/symfony-bundle"}},d={},l=[],c={toc:l},g="wrapper";function p(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,r.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/universal-service-providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/other-frameworks"},"Get started with another framework (or no framework)"))))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0db959c8.0e2c30f2.js b/assets/js/0db959c8.fb424422.js similarity index 93% rename from assets/js/0db959c8.0e2c30f2.js rename to assets/js/0db959c8.fb424422.js index 49c69a080c..5785f9945a 100644 --- a/assets/js/0db959c8.0e2c30f2.js +++ b/assets/js/0db959c8.fb424422.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2227],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),o=n(20053),i=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function g(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function y(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=d(e),[i,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[s,u]=m({queryString:n,groupId:a}),[p,y]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),h=(()=>{const e=s??p;return g({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{h&&l(h)}),[h]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var h=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),s(a))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},i,{className:(0,o.A)("tabs__item",b.tabItem,i?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function P(e){const t=y(e);return r.createElement("div",{className:(0,o.A)("tabs-container",b.tabList)},r.createElement(f,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,h.A)();return r.createElement(P,(0,a.A)({key:String(t)},e))}},89518:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),o=(n(67443),n(11470)),i=n(19365);const l={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},s=void 0,u={unversionedId:"doctrine-annotations-attributes",id:"version-5.0/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-5.0/doctrine-annotations-attributes.mdx",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/5.0/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/doctrine-annotations-attributes.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},sidebar:"version-5.0/docs",previous:{title:"Migrating",permalink:"/docs/5.0/migrating"},next:{title:"Annotations reference",permalink:"/docs/5.0/annotations-reference"}},c={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(g,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,r.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in a future release."),(0,r.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support. This was the purpose of the ',(0,r.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,r.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"Please note that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,r.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,r.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,r.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("strong",null,"Heads up!"),(0,r.yg)("p",null,"Some IDEs provide support for Doctrine annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",null,"PhpStorm via the ",(0,r.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,r.yg)("li",null,"Eclipse via the ",(0,r.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,r.yg)("li",null,"Netbeans has native support")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"We strongly recommend using an IDE that has Doctrine annotations support.\n"))),(0,r.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,r.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,r.yg)("p",null,"The same code can be written this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,r.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,r.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,r.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,r.yg)("p",null,"They support the same attributes too."),(0,r.yg)("p",null,"A few notable differences:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,r.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,r.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,r.yg)("p",null,"Let's take an example with the ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/autowiring"},(0,r.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2227],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),o=n(20053),i=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function g(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function h(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=d(e),[i,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[s,u]=m({queryString:n,groupId:a}),[p,h]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),y=(()=>{const e=s??p;return g({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),h(e)}),[u,h,o]),tabValues:o}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),s(a))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},i,{className:(0,o.A)("tabs__item",b.tabItem,i?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function P(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",b.tabList)},r.createElement(f,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,y.A)();return r.createElement(P,(0,a.A)({key:String(t)},e))}},89518:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),o=(n(67443),n(11470)),i=n(19365);const l={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},s=void 0,u={unversionedId:"doctrine-annotations-attributes",id:"version-5.0/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-5.0/doctrine-annotations-attributes.mdx",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/5.0/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/doctrine-annotations-attributes.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},sidebar:"version-5.0/docs",previous:{title:"Migrating",permalink:"/docs/5.0/migrating"},next:{title:"Annotations reference",permalink:"/docs/5.0/annotations-reference"}},c={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(g,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,r.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in a future release."),(0,r.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support. This was the purpose of the ',(0,r.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,r.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"Please note that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,r.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,r.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,r.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("strong",null,"Heads up!"),(0,r.yg)("p",null,"Some IDEs provide support for Doctrine annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",null,"PhpStorm via the ",(0,r.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,r.yg)("li",null,"Eclipse via the ",(0,r.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,r.yg)("li",null,"Netbeans has native support")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"We strongly recommend using an IDE that has Doctrine annotations support.\n"))),(0,r.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,r.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,r.yg)("p",null,"The same code can be written this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,r.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,r.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,r.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,r.yg)("p",null,"They support the same attributes too."),(0,r.yg)("p",null,"A few notable differences:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,r.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,r.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,r.yg)("p",null,"Let's take an example with the ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/autowiring"},(0,r.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0df2ba32.2e7b3274.js b/assets/js/0df2ba32.64b69ad9.js similarity index 97% rename from assets/js/0df2ba32.2e7b3274.js rename to assets/js/0df2ba32.64b69ad9.js index 1499248b1f..bc8095c459 100644 --- a/assets/js/0df2ba32.2e7b3274.js +++ b/assets/js/0df2ba32.64b69ad9.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2935],{79441:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-4.3/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/versioned_docs/version-4.3/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/4.3/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/semver.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"version-4.3/docs",previous:{title:"Annotations reference",permalink:"/docs/4.3/annotations-reference"},next:{title:"Changelog",permalink:"/docs/4.3/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2935],{79441:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-4.3/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/versioned_docs/version-4.3/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/4.3/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/semver.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"version-4.3/docs",previous:{title:"Annotations reference",permalink:"/docs/4.3/annotations-reference"},next:{title:"Changelog",permalink:"/docs/4.3/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0e5befdb.eb114fa3.js b/assets/js/0e5befdb.28a1500f.js similarity index 98% rename from assets/js/0e5befdb.eb114fa3.js rename to assets/js/0e5befdb.28a1500f.js index 8b0b95cf1e..89876c2af4 100644 --- a/assets/js/0e5befdb.eb114fa3.js +++ b/assets/js/0e5befdb.28a1500f.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[932],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),c=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function p(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function g(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function y(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=p(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=h({queryString:n,groupId:a}),[d,y]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??d;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,i.a_)(),p=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(d(t),s(a))},g=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:p},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function N(e){const t=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function T(e){const t=(0,m.A)();return r.createElement(N,(0,a.A)({key:String(t)},e))}},13814:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>p,frontMatter:()=>l,metadata:()=>o,toc:()=>u});var a=n(58168),r=(n(96540),n(15680));n(67443),n(11470),n(19365);const l={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},i=void 0,o={unversionedId:"extend-type",id:"version-6.1/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-6.1/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/6.1/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/extend-type.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"docs",previous:{title:"Autowiring services",permalink:"/docs/6.1/autowiring"},next:{title:"External type declaration",permalink:"/docs/6.1/external-type-declaration"}},s={},u=[],c={toc:u},d="wrapper";function p(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n")),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[932],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),c=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function p(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function g(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function y(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=p(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=h({queryString:n,groupId:a}),[d,y]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??d;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,i.a_)(),p=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(d(t),s(a))},g=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:p},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function N(e){const t=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function T(e){const t=(0,m.A)();return r.createElement(N,(0,a.A)({key:String(t)},e))}},13814:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>p,frontMatter:()=>l,metadata:()=>o,toc:()=>u});var a=n(58168),r=(n(96540),n(15680));n(67443),n(11470),n(19365);const l={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},i=void 0,o={unversionedId:"extend-type",id:"version-6.1/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-6.1/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/6.1/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/extend-type.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"docs",previous:{title:"Autowiring services",permalink:"/docs/6.1/autowiring"},next:{title:"External type declaration",permalink:"/docs/6.1/external-type-declaration"}},s={},u=[],c={toc:u},d="wrapper";function p(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n")),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0ef60658.e8767085.js b/assets/js/0ef60658.bf770bdb.js similarity index 99% rename from assets/js/0ef60658.e8767085.js rename to assets/js/0ef60658.bf770bdb.js index 7dfd587dad..6cc15884f2 100644 --- a/assets/js/0ef60658.e8767085.js +++ b/assets/js/0ef60658.bf770bdb.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9255],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),o=t(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),o=t(96540),i=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:o}}=e;return{value:n,label:t,attributes:a,default:o}}))}function d(e){const{values:n,children:t}=e;return(0,o.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,o.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,o.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,n)),o.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return o.createElement(v,(0,a.A)({key:String(n)},e))}},84663:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),o=(t(96540),t(15680)),i=(t(67443),t(11470)),r=t(19365);const l={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},u=void 0,s={unversionedId:"external-type-declaration",id:"version-4.2/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-4.2/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/4.2/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/external-type-declaration.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"version-4.2/docs",previous:{title:"Extending a type",permalink:"/docs/4.2/extend-type"},next:{title:"Input types",permalink:"/docs/4.2/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,o.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),")."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/4.2/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/4.2/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9255],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),o=t(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),o=t(96540),i=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:o}}=e;return{value:n,label:t,attributes:a,default:o}}))}function d(e){const{values:n,children:t}=e;return(0,o.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,o.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,o.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,n)),o.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return o.createElement(v,(0,a.A)({key:String(n)},e))}},84663:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),o=(t(96540),t(15680)),i=(t(67443),t(11470)),r=t(19365);const l={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},u=void 0,s={unversionedId:"external-type-declaration",id:"version-4.2/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-4.2/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/4.2/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/external-type-declaration.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"version-4.2/docs",previous:{title:"Extending a type",permalink:"/docs/4.2/extend-type"},next:{title:"Input types",permalink:"/docs/4.2/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,o.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),")."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/4.2/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/4.2/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0fd21208.36dd45ca.js b/assets/js/0fd21208.a724c6b4.js similarity index 80% rename from assets/js/0fd21208.36dd45ca.js rename to assets/js/0fd21208.a724c6b4.js index a29d8eb2e8..12704f75ef 100644 --- a/assets/js/0fd21208.36dd45ca.js +++ b/assets/js/0fd21208.a724c6b4.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6158],{82188:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>u,contentTitle:()=>s,default:()=>c,frontMatter:()=>o,metadata:()=>r,toc:()=>d});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},s=void 0,r={unversionedId:"mutations",id:"version-4.0/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-4.0/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/4.0/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/mutations.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},sidebar:"version-4.0/docs",previous:{title:"Queries",permalink:"/docs/4.0/queries"},next:{title:"Type mapping",permalink:"/docs/4.0/type_mapping"}},u={},d=[],l={toc:d},p="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(p,(0,a.A)({},l,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In GraphQLite, mutations are created ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/queries"},"like queries"),"."),(0,i.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6158],{82188:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>u,contentTitle:()=>s,default:()=>c,frontMatter:()=>o,metadata:()=>r,toc:()=>l});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},s=void 0,r={unversionedId:"mutations",id:"version-4.0/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-4.0/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/4.0/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/mutations.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},sidebar:"version-4.0/docs",previous:{title:"Queries",permalink:"/docs/4.0/queries"},next:{title:"Type mapping",permalink:"/docs/4.0/type_mapping"}},u={},l=[],d={toc:l},p="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(p,(0,a.A)({},d,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In GraphQLite, mutations are created ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/queries"},"like queries"),"."),(0,i.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/102de343.a2b42f70.js b/assets/js/102de343.1b48167b.js similarity index 98% rename from assets/js/102de343.a2b42f70.js rename to assets/js/102de343.1b48167b.js index a42f2b39a7..8c2d3e824a 100644 --- a/assets/js/102de343.a2b42f70.js +++ b/assets/js/102de343.1b48167b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9097],{26348:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>m});var t=a(58168),r=(a(96540),a(15680));a(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving",original_id:"argument-resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-4.1/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.1/argument_resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/4.1/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/argument_resolving.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving",original_id:"argument-resolving"},sidebar:"version-4.1/docs",previous:{title:"Custom annotations",permalink:"/docs/4.1/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/4.1/extend_input_type"}},s={},m=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],p={toc:m},g="wrapper";function d(e){let{components:n,...a}=e;return(0,r.yg)(g,(0,t.A)({},p,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," object. For instance:",(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"li"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"li",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware"))),(0,r.yg)("li",{parentName:"ul"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,r.yg)("li",{parentName:"ul"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Validate")," annotation (in Laravel package)")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middleware class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter resolver class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9097],{26348:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>m});var t=a(58168),r=(a(96540),a(15680));a(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving",original_id:"argument-resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-4.1/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.1/argument_resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/4.1/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/argument_resolving.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving",original_id:"argument-resolving"},sidebar:"version-4.1/docs",previous:{title:"Custom annotations",permalink:"/docs/4.1/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/4.1/extend_input_type"}},s={},m=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],p={toc:m},g="wrapper";function d(e){let{components:n,...a}=e;return(0,r.yg)(g,(0,t.A)({},p,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," object. For instance:",(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"li"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"li",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware"))),(0,r.yg)("li",{parentName:"ul"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,r.yg)("li",{parentName:"ul"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Validate")," annotation (in Laravel package)")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middleware class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter resolver class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/107b7a36.0c653dcc.js b/assets/js/107b7a36.f7b0c4e9.js similarity index 98% rename from assets/js/107b7a36.0c653dcc.js rename to assets/js/107b7a36.f7b0c4e9.js index 0db7f42a42..b40612ec13 100644 --- a/assets/js/107b7a36.0c653dcc.js +++ b/assets/js/107b7a36.f7b0c4e9.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6408],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),u=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==u&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",b.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},89316:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>u,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const u={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-6.0/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-6.0/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/6.0/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/query-plan.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"docs",previous:{title:"Connecting security to your framework",permalink:"/docs/6.0/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/6.0/prefetch-method"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6408],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),u=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==u&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",b.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},89316:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>u,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const u={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-6.0/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-6.0/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/6.0/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/query-plan.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"docs",previous:{title:"Connecting security to your framework",permalink:"/docs/6.0/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/6.0/prefetch-method"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/107d11ee.272fa44b.js b/assets/js/107d11ee.e951c451.js similarity index 92% rename from assets/js/107d11ee.272fa44b.js rename to assets/js/107d11ee.e951c451.js index 20b51d51e1..18e09e35b2 100644 --- a/assets/js/107d11ee.272fa44b.js +++ b/assets/js/107d11ee.e951c451.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6473],{32626:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-6.0/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-6.0/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/6.0/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/universal-service-providers.md",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"docs",previous:{title:"Laravel package",permalink:"/docs/6.0/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/6.0/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/6.0/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6473],{32626:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>p});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-6.0/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-6.0/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/6.0/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/universal-service-providers.md",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"docs",previous:{title:"Laravel package",permalink:"/docs/6.0/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/6.0/other-frameworks"}},l={},p=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:p},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/6.0/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/12d3ef9e.563d2fda.js b/assets/js/12d3ef9e.e80151c3.js similarity index 98% rename from assets/js/12d3ef9e.563d2fda.js rename to assets/js/12d3ef9e.e80151c3.js index 2b73e222dc..8528605982 100644 --- a/assets/js/12d3ef9e.563d2fda.js +++ b/assets/js/12d3ef9e.e80151c3.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7042],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},39972:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},s=void 0,u={unversionedId:"extend-type",id:"version-7.0.0/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-7.0.0/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/extend-type.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"docs",previous:{title:"Autowiring services",permalink:"/docs/autowiring"},next:{title:"External type declaration",permalink:"/docs/external-type-declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7042],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},39972:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},s=void 0,u={unversionedId:"extend-type",id:"version-7.0.0/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-7.0.0/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/extend-type.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"docs",previous:{title:"Autowiring services",permalink:"/docs/autowiring"},next:{title:"External type declaration",permalink:"/docs/external-type-declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/136c1ee9.c16d163a.js b/assets/js/136c1ee9.5b3b5c84.js similarity index 99% rename from assets/js/136c1ee9.c16d163a.js rename to assets/js/136c1ee9.5b3b5c84.js index f60416f8ce..f6345372fc 100644 --- a/assets/js/136c1ee9.c16d163a.js +++ b/assets/js/136c1ee9.5b3b5c84.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6086],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>V});var n=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),d=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function c(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??p(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function m(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:t}=e;const n=(0,l.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function y(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!m({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=h({queryString:t,groupId:n}),[p,y]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,d.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),g=(()=>{const e=s??p;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,i]),tabValues:i}}var g=t(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:a,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),c=e=>{const a=e.currentTarget,t=d.indexOf(a),n=u[t].value;n!==l&&(p(a),s(n))},m=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const t=d.indexOf(e.currentTarget)+1;a=d[t]??d[0];break}case"ArrowLeft":{const t=d.indexOf(e.currentTarget)-1;a=d[t]??d[d.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===a?0:-1,"aria-selected":l===a,key:a,ref:e=>d.push(e),onKeyDown:m,onClick:c},o,{className:(0,i.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=y(e);return r.createElement("div",{className:(0,i.A)("tabs-container",v.tabList)},r.createElement(f,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function V(e){const a=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},17317:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-4.2/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-4.2/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/4.2/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/validation.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"version-4.2/docs",previous:{title:"Error handling",permalink:"/docs/4.2/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/4.2/authentication-authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],c={toc:p},m="wrapper";function h(e){let{components:a,...t}=e;return(0,r.yg)(m,(0,n.A)({},c,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,r.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,r.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,r.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,r.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,r.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,r.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,r.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,r.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,r.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,r.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,r.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,r.yg)("p",null,"If the data entered by the user is ",(0,r.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,r.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\Graphqlite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,r.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,r.yg)("p",null,"You can also pass an array to the ",(0,r.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,r.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6086],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>V});var n=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),d=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function c(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??p(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function m(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:t}=e;const n=(0,l.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function y(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!m({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=h({queryString:t,groupId:n}),[p,y]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,d.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),g=(()=>{const e=s??p;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,i]),tabValues:i}}var g=t(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:a,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),c=e=>{const a=e.currentTarget,t=d.indexOf(a),n=u[t].value;n!==l&&(p(a),s(n))},m=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const t=d.indexOf(e.currentTarget)+1;a=d[t]??d[0];break}case"ArrowLeft":{const t=d.indexOf(e.currentTarget)-1;a=d[t]??d[d.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===a?0:-1,"aria-selected":l===a,key:a,ref:e=>d.push(e),onKeyDown:m,onClick:c},o,{className:(0,i.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=y(e);return r.createElement("div",{className:(0,i.A)("tabs-container",v.tabList)},r.createElement(f,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function V(e){const a=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},17317:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-4.2/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-4.2/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/4.2/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/validation.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"version-4.2/docs",previous:{title:"Error handling",permalink:"/docs/4.2/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/4.2/authentication-authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],c={toc:p},m="wrapper";function h(e){let{components:a,...t}=e;return(0,r.yg)(m,(0,n.A)({},c,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,r.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,r.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,r.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,r.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,r.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,r.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,r.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,r.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,r.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,r.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,r.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,r.yg)("p",null,"If the data entered by the user is ",(0,r.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,r.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\Graphqlite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,r.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,r.yg)("p",null,"You can also pass an array to the ",(0,r.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,r.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/13b4aeb1.56d9e47f.js b/assets/js/13b4aeb1.d2956e3d.js similarity index 96% rename from assets/js/13b4aeb1.56d9e47f.js rename to assets/js/13b4aeb1.d2956e3d.js index b97eb8e65e..461153784d 100644 --- a/assets/js/13b4aeb1.56d9e47f.js +++ b/assets/js/13b4aeb1.d2956e3d.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6088],{43799:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>o,metadata:()=>r,toc:()=>p});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},s=void 0,r={unversionedId:"pagination",id:"version-3.0/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-3.0/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/3.0/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/pagination.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},sidebar:"version-3.0/docs",previous:{title:"File uploads",permalink:"/docs/3.0/file-uploads"},next:{title:"Custom output types",permalink:"/docs/3.0/custom-output-types"}},l={},p=[{value:"Usage",id:"usage",level:2}],u={toc:p},g="wrapper";function d(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,i.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,i.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,i.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,i.yg)("h2",{id:"usage"},"Usage"),(0,i.yg)("p",null,"In your query, simply return a class that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\ORMQueryResult($doctrineQuery);\n }\n}\n")),(0,i.yg)("p",null,"Notice that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,i.yg)("li",{parentName:"ul"},"you MUST add a ",(0,i.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,i.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,i.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,i.yg)("p",null,'The "count" field returns the ',(0,i.yg)("strong",{parentName:"p"},"total count")," of items."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6088],{43799:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>o,metadata:()=>r,toc:()=>p});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},s=void 0,r={unversionedId:"pagination",id:"version-3.0/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-3.0/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/3.0/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/pagination.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},sidebar:"version-3.0/docs",previous:{title:"File uploads",permalink:"/docs/3.0/file-uploads"},next:{title:"Custom output types",permalink:"/docs/3.0/custom-output-types"}},l={},p=[{value:"Usage",id:"usage",level:2}],u={toc:p},g="wrapper";function d(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,i.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,i.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,i.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,i.yg)("h2",{id:"usage"},"Usage"),(0,i.yg)("p",null,"In your query, simply return a class that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\ORMQueryResult($doctrineQuery);\n }\n}\n")),(0,i.yg)("p",null,"Notice that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,i.yg)("li",{parentName:"ul"},"you MUST add a ",(0,i.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,i.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,i.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,i.yg)("p",null,'The "count" field returns the ',(0,i.yg)("strong",{parentName:"p"},"total count")," of items."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1428bdad.9d14ab8c.js b/assets/js/1428bdad.014df04c.js similarity index 94% rename from assets/js/1428bdad.9d14ab8c.js rename to assets/js/1428bdad.014df04c.js index c6c354cf35..a1cd2a4feb 100644 --- a/assets/js/1428bdad.9d14ab8c.js +++ b/assets/js/1428bdad.014df04c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3723],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),s=a(57485),i=a(31682),u=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[s,i]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,u.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=s??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const u=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=u.indexOf(t),n=i[a].value;n!==l&&(y(t),s(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=u.indexOf(e.currentTarget)+1;t=u[a]??u[0];break}case"ArrowLeft":{const a=u.indexOf(e.currentTarget)-1;t=u[a]??u[u.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>u.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},46618:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>c,frontMatter:()=>p,metadata:()=>l,toc:()=>i});var n=a(58168),r=(a(96540),a(15680));a(67443),a(11470),a(19365);const p={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},o=void 0,l={unversionedId:"custom-types",id:"version-6.1/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-6.1/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/6.1/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/custom-types.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"docs",previous:{title:"Pagination",permalink:"/docs/6.1/pagination"},next:{title:"Custom annotations",permalink:"/docs/6.1/field-middlewares"}},s={},i=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],u={toc:i},y="wrapper";function c(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n')),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3723],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),s=a(57485),i=a(31682),u=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function y(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=y(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[s,i]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,u.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=s??c;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const u=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),y=e=>{const t=e.currentTarget,a=u.indexOf(t),n=i[a].value;n!==l&&(c(t),s(n))},d=e=>{let t=null;switch(e.key){case"Enter":y(e);break;case"ArrowRight":{const a=u.indexOf(e.currentTarget)+1;t=u[a]??u[0];break}case"ArrowLeft":{const a=u.indexOf(e.currentTarget)-1;t=u[a]??u[u.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>u.push(e),onKeyDown:d,onClick:y},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},46618:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>y,frontMatter:()=>p,metadata:()=>l,toc:()=>i});var n=a(58168),r=(a(96540),a(15680));a(67443),a(11470),a(19365);const p={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},o=void 0,l={unversionedId:"custom-types",id:"version-6.1/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-6.1/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/6.1/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/custom-types.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"docs",previous:{title:"Pagination",permalink:"/docs/6.1/pagination"},next:{title:"Custom annotations",permalink:"/docs/6.1/field-middlewares"}},s={},i=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],u={toc:i},c="wrapper";function y(e){let{components:t,...a}=e;return(0,r.yg)(c,(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n')),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/143f7888.154e6dae.js b/assets/js/143f7888.0030da3c.js similarity index 98% rename from assets/js/143f7888.154e6dae.js rename to assets/js/143f7888.0030da3c.js index 894614543b..fe01562dce 100644 --- a/assets/js/143f7888.154e6dae.js +++ b/assets/js/143f7888.0030da3c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5198],{16733:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating",original_id:"migrating"},r=void 0,l={unversionedId:"migrating",id:"version-4.1/migrating",title:"Migrating",description:"Migrating from v4.0 to v4.1",source:"@site/versioned_docs/version-4.1/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/4.1/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/migrating.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating",original_id:"migrating"},sidebar:"version-4.1/docs",previous:{title:"Troubleshooting",permalink:"/docs/4.1/troubleshooting"},next:{title:"Annotations VS Attributes",permalink:"/docs/4.1/doctrine-annotations-attributes"}},s={},g=[{value:"Migrating from v4.0 to v4.1",id:"migrating-from-v40-to-v41",level:2},{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},p="wrapper";function u(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v40-to-v41"},"Migrating from v4.0 to v4.1"),(0,i.yg)("p",null,"GraphQLite follows Semantic Versioning. GraphQLite 4.1 is backward compatible with GraphQLite 4.0. See\n",(0,i.yg)("a",{parentName:"p",href:"/docs/4.1/semver"},"semantic versioning")," for more details."),(0,i.yg)("p",null,"There is one exception though: the ",(0,i.yg)("strong",{parentName:"p"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL\ninput types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"p"},"composer.json")," by running this command:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/4.1/annotations_reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5198],{16733:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating",original_id:"migrating"},r=void 0,l={unversionedId:"migrating",id:"version-4.1/migrating",title:"Migrating",description:"Migrating from v4.0 to v4.1",source:"@site/versioned_docs/version-4.1/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/4.1/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/migrating.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating",original_id:"migrating"},sidebar:"version-4.1/docs",previous:{title:"Troubleshooting",permalink:"/docs/4.1/troubleshooting"},next:{title:"Annotations VS Attributes",permalink:"/docs/4.1/doctrine-annotations-attributes"}},s={},g=[{value:"Migrating from v4.0 to v4.1",id:"migrating-from-v40-to-v41",level:2},{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},p="wrapper";function u(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v40-to-v41"},"Migrating from v4.0 to v4.1"),(0,i.yg)("p",null,"GraphQLite follows Semantic Versioning. GraphQLite 4.1 is backward compatible with GraphQLite 4.0. See\n",(0,i.yg)("a",{parentName:"p",href:"/docs/4.1/semver"},"semantic versioning")," for more details."),(0,i.yg)("p",null,"There is one exception though: the ",(0,i.yg)("strong",{parentName:"p"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL\ninput types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"p"},"composer.json")," by running this command:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/4.1/annotations_reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/15a79915.fee0e891.js b/assets/js/15a79915.9786fff1.js similarity index 99% rename from assets/js/15a79915.fee0e891.js rename to assets/js/15a79915.9786fff1.js index 1fd17ab62d..a61c77cee6 100644 --- a/assets/js/15a79915.fee0e891.js +++ b/assets/js/15a79915.9786fff1.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6729],{19365:(e,a,t)=>{t.d(a,{A:()=>i});var n=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:t,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function d(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function g(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:t}=e;const n=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(n.location.search);a.set(l,e),n.replace({...n.location,search:a.toString()})}),[l,n])]}function m(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:t,groupId:n}),[c,m]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,l]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:n}),y=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==o&&(c(a),s(n))},g=e=>{let a=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function T(e){const a=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},40470:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-7.0.0/laravel-package-advanced",title:"Laravel package: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-7.0.0/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/laravel-package-advanced.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},sidebar:"docs",previous:{title:"Symfony specific features",permalink:"/docs/symfony-bundle-advanced"},next:{title:"Internals",permalink:"/docs/internals"}},p={},c=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3},{value:"Export the schema from the CLI",id:"export-the-schema-from-the-cli",level:2}],d={toc:c},g="wrapper";function h(e){let{components:a,...t}=e;return(0,r.yg)(g,(0,n.A)({},d,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n'))),(0,r.yg)("h2",{id:"export-the-schema-from-the-cli"},"Export the schema from the CLI"),(0,r.yg)("p",null,"The extension comes with a special command: ",(0,r.yg)("inlineCode",{parentName:"p"},"graphqlite:export-schema"),"."),(0,r.yg)("p",null,"Usage:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ ./artisan graphqlite:export-schema --output=schema.graphql\n")),(0,r.yg)("p",null,"This will export your GraphQL schema in SDL format. You can use this exported schema to import it in other\ntools (like graphql-codegen)."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6729],{19365:(e,a,t)=>{t.d(a,{A:()=>i});var n=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:t,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function d(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function g(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:t}=e;const n=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(n.location.search);a.set(l,e),n.replace({...n.location,search:a.toString()})}),[l,n])]}function m(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:t,groupId:n}),[c,m]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,l]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:n}),y=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==o&&(c(a),s(n))},g=e=>{let a=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function T(e){const a=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},40470:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-7.0.0/laravel-package-advanced",title:"Laravel package: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-7.0.0/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/laravel-package-advanced.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},sidebar:"docs",previous:{title:"Symfony specific features",permalink:"/docs/symfony-bundle-advanced"},next:{title:"Internals",permalink:"/docs/internals"}},p={},c=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3},{value:"Export the schema from the CLI",id:"export-the-schema-from-the-cli",level:2}],d={toc:c},g="wrapper";function h(e){let{components:a,...t}=e;return(0,r.yg)(g,(0,n.A)({},d,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n'))),(0,r.yg)("h2",{id:"export-the-schema-from-the-cli"},"Export the schema from the CLI"),(0,r.yg)("p",null,"The extension comes with a special command: ",(0,r.yg)("inlineCode",{parentName:"p"},"graphqlite:export-schema"),"."),(0,r.yg)("p",null,"Usage:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ ./artisan graphqlite:export-schema --output=schema.graphql\n")),(0,r.yg)("p",null,"This will export your GraphQL schema in SDL format. You can use this exported schema to import it in other\ntools (like graphql-codegen)."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/15b5a907.83328341.js b/assets/js/15b5a907.5de5d297.js similarity index 98% rename from assets/js/15b5a907.83328341.js rename to assets/js/15b5a907.5de5d297.js index 6acd97a978..d0c7f6ba6c 100644 --- a/assets/js/15b5a907.83328341.js +++ b/assets/js/15b5a907.5de5d297.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8023],{16871:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"version-7.0.0/laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-7.0.0/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/laravel-package.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"docs",previous:{title:"Symfony bundle",permalink:"/docs/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},'$ php artisan vendor:publish --provider="TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider"\n')),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8023],{16871:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"version-7.0.0/laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-7.0.0/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/laravel-package.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"docs",previous:{title:"Symfony bundle",permalink:"/docs/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},'$ php artisan vendor:publish --provider="TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider"\n')),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/16017aa6.d535d4c6.js b/assets/js/16017aa6.25f7826c.js similarity index 99% rename from assets/js/16017aa6.d535d4c6.js rename to assets/js/16017aa6.25f7826c.js index 7ad33303e3..867828c272 100644 --- a/assets/js/16017aa6.d535d4c6.js +++ b/assets/js/16017aa6.25f7826c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4366],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),l=n(20053);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),l=n(96540),r=n(20053),i=n(23104),o=n(56347),u=n(57485),p=n(31682),s=n(89466);function c(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:l}}=e;return{value:t,label:n,attributes:a,default:l}}))}function d(e){const{values:t,children:n}=e;return(0,l.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=d(e),[i,o]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[u,p]=g({queryString:n,groupId:a}),[c,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,s.Dv)(n);return[a,(0,l.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),h=(()=>{const e=u??c;return y({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:i,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),p(e),m(e)}),[p,m,r]),tabValues:r}}var h=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:u,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=s.indexOf(t),a=p[n].value;a!==o&&(c(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=s.indexOf(e.currentTarget)+1;t=s[n]??s[0];break}case"ArrowLeft":{const n=s.indexOf(e.currentTarget)-1;t=s[n]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:i}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:d},i,{className:(0,r.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function I(e){const t=m(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,t)),l.createElement(v,(0,a.A)({},e,t)))}function T(e){const t=(0,h.A)();return l.createElement(I,(0,a.A)({key:String(t)},e))}},62503:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>u,default:()=>g,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var a=n(58168),l=(n(96540),n(15680)),r=(n(67443),n(11470)),i=n(19365);const o={id:"input-types",title:"Input types",sidebar_label:"Input types"},u=void 0,p={unversionedId:"input-types",id:"version-4.3/input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-4.3/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/4.3/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/input-types.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"version-4.3/docs",previous:{title:"External type declaration",permalink:"/docs/4.3/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/4.3/inheritance-interfaces"}},s={},c=[{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3},{value:"@Input Annotation",id:"input-annotation",level:2},{value:"Multiple input types per one class",id:"multiple-input-types-per-one-class",level:3}],d={toc:c},y="wrapper";function g(e){let{components:t,...n}=e;return(0,l.yg)(y,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,l.yg)("p",null,"Your GraphQL query might look like this:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,l.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,l.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,l.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,l.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using ",(0,l.yg)("strong",{parentName:"p"},"Factory")," or annotating the class with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),"."),(0,l.yg)("h2",{id:"factory"},"Factory"),(0,l.yg)("p",null,"A ",(0,l.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,l.yg)("p",null,"Here is an example of factory:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")))),(0,l.yg)("p",null,"and now, you can run query like this:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,l.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,l.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,l.yg)("p",null,"A few important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,l.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,l.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,l.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,l.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")))),(0,l.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')))),(0,l.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,l.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,l.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,l.yg)("p",null,"You can use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,l.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')))),(0,l.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,l.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,l.yg)("p",null,"Here is an annotated sample:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')))),(0,l.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,l.yg)("p",null,"Image your ",(0,l.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,l.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')))),(0,l.yg)("p",null,"With the ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,l.yg)("p",null,"To be able to hide an argument, the argument must have a default value."),(0,l.yg)("h2",{id:"input-annotation"},"@Input Annotation"),(0,l.yg)("p",null,"Let's transform ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," class into an input type by adding ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation to it and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to corresponding properties:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Input\n */\nclass Location\n{\n\n /**\n * @Field\n * @var float\n */\n private $latitude;\n\n /**\n * @Field\n * @var float\n */\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"Now if you call ",(0,l.yg)("inlineCode",{parentName:"p"},"getCities()")," query you can pass the location input in the same way as with factories.\nThe ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with provided ",(0,l.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,l.yg)("inlineCode",{parentName:"p"},"longitude")," and passed to the controller as a parameter."),(0,l.yg)("p",null,"There are some important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"@Field")," annotation is recognized only on properties for Input Type."),(0,l.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,l.yg)("ul",{parentName:"li"},(0,l.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,l.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort."),(0,l.yg)("li",{parentName:"ul"},"For private or protected properties implemented public setter is required (if they are not set via constructor). For example ",(0,l.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),".")))),(0,l.yg)("h3",{id:"multiple-input-types-per-one-class"},"Multiple input types per one class"),(0,l.yg)("p",null,"Simple usage of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),' annotation on a class creates an GraphQl input named by class name + "Input" suffix if a class name does not end with it already.\nYou can add multiple ',(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotations to the same class, give them different names and link different fields.\nConsider the following example:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n #[Field]\n public ?int $age;\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Input(name="CreateUserInput", default=true)\n * @Input(name="UpdateUserInput", update=true)\n */\nclass UserInput\n{\n\n /**\n * @Field()\n * @var string\n */\n public $username;\n\n /**\n * @Field(for="CreateUserInput")\n * @var string\n */\n public string $email;\n\n /**\n * @Field(for="CreateUserInput", inputType="String!")\n * @Field(for="UpdateUserInput", inputType="String")\n * @var string|null\n */\n public $password;\n\n /**\n * @Field()\n * @var int|null\n */\n public $age;\n}\n')))),(0,l.yg)("p",null,"There are 2 input types created for just one class: ",(0,l.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,l.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,l.yg)("p",null,"Note that ",(0,l.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,l.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 7 they will be set to ",(0,l.yg)("inlineCode",{parentName:"p"},"null"),", while in PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4366],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),l=n(20053);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),l=n(96540),r=n(20053),i=n(23104),o=n(56347),u=n(57485),p=n(31682),s=n(89466);function c(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:l}}=e;return{value:t,label:n,attributes:a,default:l}}))}function d(e){const{values:t,children:n}=e;return(0,l.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=d(e),[i,o]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[u,p]=g({queryString:n,groupId:a}),[c,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,s.Dv)(n);return[a,(0,l.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),h=(()=>{const e=u??c;return y({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:i,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),p(e),m(e)}),[p,m,r]),tabValues:r}}var h=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:u,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=s.indexOf(t),a=p[n].value;a!==o&&(c(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=s.indexOf(e.currentTarget)+1;t=s[n]??s[0];break}case"ArrowLeft":{const n=s.indexOf(e.currentTarget)-1;t=s[n]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:i}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:d},i,{className:(0,r.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function I(e){const t=m(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,t)),l.createElement(v,(0,a.A)({},e,t)))}function T(e){const t=(0,h.A)();return l.createElement(I,(0,a.A)({key:String(t)},e))}},62503:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>u,default:()=>g,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var a=n(58168),l=(n(96540),n(15680)),r=(n(67443),n(11470)),i=n(19365);const o={id:"input-types",title:"Input types",sidebar_label:"Input types"},u=void 0,p={unversionedId:"input-types",id:"version-4.3/input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-4.3/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/4.3/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/input-types.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"version-4.3/docs",previous:{title:"External type declaration",permalink:"/docs/4.3/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/4.3/inheritance-interfaces"}},s={},c=[{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3},{value:"@Input Annotation",id:"input-annotation",level:2},{value:"Multiple input types per one class",id:"multiple-input-types-per-one-class",level:3}],d={toc:c},y="wrapper";function g(e){let{components:t,...n}=e;return(0,l.yg)(y,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,l.yg)("p",null,"Your GraphQL query might look like this:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,l.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,l.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,l.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,l.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using ",(0,l.yg)("strong",{parentName:"p"},"Factory")," or annotating the class with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),"."),(0,l.yg)("h2",{id:"factory"},"Factory"),(0,l.yg)("p",null,"A ",(0,l.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,l.yg)("p",null,"Here is an example of factory:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")))),(0,l.yg)("p",null,"and now, you can run query like this:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,l.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,l.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,l.yg)("p",null,"A few important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,l.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,l.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,l.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,l.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")))),(0,l.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')))),(0,l.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,l.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,l.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,l.yg)("p",null,"You can use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,l.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')))),(0,l.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,l.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,l.yg)("p",null,"Here is an annotated sample:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')))),(0,l.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,l.yg)("p",null,"Image your ",(0,l.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,l.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')))),(0,l.yg)("p",null,"With the ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,l.yg)("p",null,"To be able to hide an argument, the argument must have a default value."),(0,l.yg)("h2",{id:"input-annotation"},"@Input Annotation"),(0,l.yg)("p",null,"Let's transform ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," class into an input type by adding ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation to it and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to corresponding properties:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Input\n */\nclass Location\n{\n\n /**\n * @Field\n * @var float\n */\n private $latitude;\n\n /**\n * @Field\n * @var float\n */\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"Now if you call ",(0,l.yg)("inlineCode",{parentName:"p"},"getCities()")," query you can pass the location input in the same way as with factories.\nThe ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with provided ",(0,l.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,l.yg)("inlineCode",{parentName:"p"},"longitude")," and passed to the controller as a parameter."),(0,l.yg)("p",null,"There are some important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"@Field")," annotation is recognized only on properties for Input Type."),(0,l.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,l.yg)("ul",{parentName:"li"},(0,l.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,l.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort."),(0,l.yg)("li",{parentName:"ul"},"For private or protected properties implemented public setter is required (if they are not set via constructor). For example ",(0,l.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),".")))),(0,l.yg)("h3",{id:"multiple-input-types-per-one-class"},"Multiple input types per one class"),(0,l.yg)("p",null,"Simple usage of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),' annotation on a class creates an GraphQl input named by class name + "Input" suffix if a class name does not end with it already.\nYou can add multiple ',(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotations to the same class, give them different names and link different fields.\nConsider the following example:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n #[Field]\n public ?int $age;\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Input(name="CreateUserInput", default=true)\n * @Input(name="UpdateUserInput", update=true)\n */\nclass UserInput\n{\n\n /**\n * @Field()\n * @var string\n */\n public $username;\n\n /**\n * @Field(for="CreateUserInput")\n * @var string\n */\n public string $email;\n\n /**\n * @Field(for="CreateUserInput", inputType="String!")\n * @Field(for="UpdateUserInput", inputType="String")\n * @var string|null\n */\n public $password;\n\n /**\n * @Field()\n * @var int|null\n */\n public $age;\n}\n')))),(0,l.yg)("p",null,"There are 2 input types created for just one class: ",(0,l.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,l.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,l.yg)("p",null,"Note that ",(0,l.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,l.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 7 they will be set to ",(0,l.yg)("inlineCode",{parentName:"p"},"null"),", while in PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/16565e6a.23cc4bae.js b/assets/js/16565e6a.6e039561.js similarity index 98% rename from assets/js/16565e6a.23cc4bae.js rename to assets/js/16565e6a.6e039561.js index f484df22b6..5f3adc2b21 100644 --- a/assets/js/16565e6a.23cc4bae.js +++ b/assets/js/16565e6a.6e039561.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3962],{96546:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>m});var t=a(58168),r=(a(96540),a(15680));a(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-3.0/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-3.0/argument_resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/3.0/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/argument_resolving.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"}},s={},m=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],p={toc:m},g="wrapper";function u(e){let{components:n,...a}=e;return(0,r.yg)(g,(0,t.A)({},p,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," object. For instance:",(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"li"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"li",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware"))),(0,r.yg)("li",{parentName:"ul"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,r.yg)("li",{parentName:"ul"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Validate")," annotation (in Laravel package)")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middleware class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter resolver class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3962],{96546:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>m});var t=a(58168),r=(a(96540),a(15680));a(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-3.0/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-3.0/argument_resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/3.0/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/argument_resolving.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"}},s={},m=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],p={toc:m},g="wrapper";function u(e){let{components:n,...a}=e;return(0,r.yg)(g,(0,t.A)({},p,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," object. For instance:",(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"li"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"li",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware"))),(0,r.yg)("li",{parentName:"ul"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,r.yg)("li",{parentName:"ul"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Validate")," annotation (in Laravel package)")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middleware class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter resolver class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/17518879.44a8bc9b.js b/assets/js/17518879.07b10d98.js similarity index 98% rename from assets/js/17518879.44a8bc9b.js rename to assets/js/17518879.07b10d98.js index 56b10ac954..b139ca4632 100644 --- a/assets/js/17518879.44a8bc9b.js +++ b/assets/js/17518879.07b10d98.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8873],{68523:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>d,contentTitle:()=>r,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var t=n(58168),i=(n(96540),n(15680));n(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"version-4.3/changelog",title:"Changelog",description:"4.3.0",source:"@site/versioned_docs/version-4.3/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/4.3/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/CHANGELOG.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"version-4.3/docs",previous:{title:"Semantic versioning",permalink:"/docs/4.3/semver"}},d={},p=[{value:"4.3.0",id:"430",level:2},{value:"Breaking change:",id:"breaking-change",level:4},{value:"Minor changes:",id:"minor-changes",level:4},{value:"4.2.0",id:"420",level:2},{value:"Breaking change:",id:"breaking-change-1",level:4},{value:"New features:",id:"new-features",level:4},{value:"4.1.0",id:"410",level:2},{value:"Breaking change:",id:"breaking-change-2",level:4},{value:"New features:",id:"new-features-1",level:4},{value:"Minor changes:",id:"minor-changes-1",level:4},{value:"Miscellaneous:",id:"miscellaneous",level:4},{value:"4.0.0",id:"400",level:2},{value:"New features:",id:"new-features-2",level:4},{value:"Symfony:",id:"symfony",level:4},{value:"Laravel:",id:"laravel",level:4},{value:"Internals:",id:"internals",level:4}],s={toc:p},u="wrapper";function g(e){let{components:a,...n}=e;return(0,i.yg)(u,(0,t.A)({},s,n,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"430"},"4.3.0"),(0,i.yg)("h4",{id:"breaking-change"},"Breaking change:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The method ",(0,i.yg)("inlineCode",{parentName:"li"},"setAnnotationCacheDir($directory)")," has been removed from the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),". The annotation\ncache will use your ",(0,i.yg)("inlineCode",{parentName:"li"},"Psr\\SimpleCache\\CacheInterface")," compliant cache handler set through the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),"\nconstructor.")),(0,i.yg)("h4",{id:"minor-changes"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Removed dependency for doctrine/cache and unified some of the cache layers following a PSR interface."),(0,i.yg)("li",{parentName:"ul"},"Cleaned up some of the documentation in an attempt to get things accurate with versioned releases.")),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h4",{id:"breaking-change-1"},"Breaking change:"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h4",{id:"new-features"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"@Type")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/input-types#input-annotation"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Logged"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h4",{id:"breaking-change-2"},"Breaking change:"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h4",{id:"new-features-1"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h4",{id:"minor-changes-1"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h4",{id:"miscellaneous"},"Miscellaneous:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h4",{id:"new-features-2"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h4",{id:"symfony"},"Symfony:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h4",{id:"laravel"},"Laravel:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h4",{id:"internals"},"Internals:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8873],{68523:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>d,contentTitle:()=>r,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var t=n(58168),i=(n(96540),n(15680));n(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"version-4.3/changelog",title:"Changelog",description:"4.3.0",source:"@site/versioned_docs/version-4.3/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/4.3/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/CHANGELOG.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"version-4.3/docs",previous:{title:"Semantic versioning",permalink:"/docs/4.3/semver"}},d={},p=[{value:"4.3.0",id:"430",level:2},{value:"Breaking change:",id:"breaking-change",level:4},{value:"Minor changes:",id:"minor-changes",level:4},{value:"4.2.0",id:"420",level:2},{value:"Breaking change:",id:"breaking-change-1",level:4},{value:"New features:",id:"new-features",level:4},{value:"4.1.0",id:"410",level:2},{value:"Breaking change:",id:"breaking-change-2",level:4},{value:"New features:",id:"new-features-1",level:4},{value:"Minor changes:",id:"minor-changes-1",level:4},{value:"Miscellaneous:",id:"miscellaneous",level:4},{value:"4.0.0",id:"400",level:2},{value:"New features:",id:"new-features-2",level:4},{value:"Symfony:",id:"symfony",level:4},{value:"Laravel:",id:"laravel",level:4},{value:"Internals:",id:"internals",level:4}],s={toc:p},u="wrapper";function g(e){let{components:a,...n}=e;return(0,i.yg)(u,(0,t.A)({},s,n,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"430"},"4.3.0"),(0,i.yg)("h4",{id:"breaking-change"},"Breaking change:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The method ",(0,i.yg)("inlineCode",{parentName:"li"},"setAnnotationCacheDir($directory)")," has been removed from the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),". The annotation\ncache will use your ",(0,i.yg)("inlineCode",{parentName:"li"},"Psr\\SimpleCache\\CacheInterface")," compliant cache handler set through the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),"\nconstructor.")),(0,i.yg)("h4",{id:"minor-changes"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Removed dependency for doctrine/cache and unified some of the cache layers following a PSR interface."),(0,i.yg)("li",{parentName:"ul"},"Cleaned up some of the documentation in an attempt to get things accurate with versioned releases.")),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h4",{id:"breaking-change-1"},"Breaking change:"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h4",{id:"new-features"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"@Type")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/input-types#input-annotation"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Logged"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h4",{id:"breaking-change-2"},"Breaking change:"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h4",{id:"new-features-1"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h4",{id:"minor-changes-1"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h4",{id:"miscellaneous"},"Miscellaneous:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h4",{id:"new-features-2"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h4",{id:"symfony"},"Symfony:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h4",{id:"laravel"},"Laravel:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h4",{id:"internals"},"Internals:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1774.8c292c27.js b/assets/js/1774.9b58a5d2.js similarity index 93% rename from assets/js/1774.8c292c27.js rename to assets/js/1774.9b58a5d2.js index 23c3e4bd42..6f73b36447 100644 --- a/assets/js/1774.8c292c27.js +++ b/assets/js/1774.9b58a5d2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1774],{81774:(e,t,n)=>{n.r(t),n.d(t,{default:()=>i});var a=n(96540),l=n(21312),o=n(69024),r=n(78511);function i(){return a.createElement(a.Fragment,null,a.createElement(o.be,{title:(0,l.T)({id:"theme.NotFound.title",message:"Page Not Found"})}),a.createElement(r.A,null,a.createElement("main",{className:"container margin-vert--xl"},a.createElement("div",{className:"row"},a.createElement("div",{className:"col col--6 col--offset-3"},a.createElement("h1",{className:"hero__title"},a.createElement(l.A,{id:"theme.NotFound.title",description:"The title of the 404 page"},"Page Not Found")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page"},"We could not find what you were looking for.")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page"},"Please contact the owner of the site that linked you to the original URL and let them know their link is broken.")))))))}}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1774],{81774:(e,t,n)=>{n.r(t),n.d(t,{default:()=>i});var a=n(96540),l=n(21312),o=n(69024),r=n(98956);function i(){return a.createElement(a.Fragment,null,a.createElement(o.be,{title:(0,l.T)({id:"theme.NotFound.title",message:"Page Not Found"})}),a.createElement(r.A,null,a.createElement("main",{className:"container margin-vert--xl"},a.createElement("div",{className:"row"},a.createElement("div",{className:"col col--6 col--offset-3"},a.createElement("h1",{className:"hero__title"},a.createElement(l.A,{id:"theme.NotFound.title",description:"The title of the 404 page"},"Page Not Found")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page"},"We could not find what you were looking for.")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page"},"Please contact the owner of the site that linked you to the original URL and let them know their link is broken.")))))))}}}]); \ No newline at end of file diff --git a/assets/js/17cca601.ca1dfe7d.js b/assets/js/17cca601.e153a41a.js similarity index 98% rename from assets/js/17cca601.ca1dfe7d.js rename to assets/js/17cca601.e153a41a.js index b40a94891d..dd1916bb0f 100644 --- a/assets/js/17cca601.ca1dfe7d.js +++ b/assets/js/17cca601.e153a41a.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6053],{90333:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>r,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var t=a(58168),i=(a(96540),a(15680));a(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"version-7.0.0/changelog",title:"Changelog",description:"7.0.0",source:"@site/versioned_docs/version-7.0.0/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/CHANGELOG.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"docs",previous:{title:"Semantic versioning",permalink:"/docs/semver"}},p={},s=[{value:"7.0.0",id:"700",level:2},{value:"Breaking Changes",id:"breaking-changes",level:3},{value:"New Features",id:"new-features",level:3},{value:"Improvements",id:"improvements",level:3},{value:"Minor Changes",id:"minor-changes",level:3},{value:"6.2.3",id:"623",level:2},{value:"6.2.2",id:"622",level:2},{value:"6.2.1",id:"621",level:2},{value:"6.2.0",id:"620",level:2},{value:"6.1.0",id:"610",level:2},{value:"Breaking Changes",id:"breaking-changes-1",level:3},{value:"Improvements",id:"improvements-1",level:3},{value:"5.0.0",id:"500",level:2},{value:"Dependencies",id:"dependencies",level:3},{value:"4.3.0",id:"430",level:2},{value:"Breaking change",id:"breaking-change",level:3},{value:"Minor changes",id:"minor-changes-1",level:3},{value:"4.2.0",id:"420",level:2},{value:"Breaking change",id:"breaking-change-1",level:3},{value:"New features",id:"new-features-1",level:3},{value:"4.1.0",id:"410",level:2},{value:"Breaking change",id:"breaking-change-2",level:3},{value:"New features",id:"new-features-2",level:3},{value:"Minor changes",id:"minor-changes-2",level:3},{value:"Miscellaneous",id:"miscellaneous",level:3},{value:"4.0.0",id:"400",level:2},{value:"New features",id:"new-features-3",level:3},{value:"Symfony",id:"symfony",level:3},{value:"Laravel",id:"laravel",level:3},{value:"Internals",id:"internals",level:3}],d={toc:s},u="wrapper";function g(e){let{components:n,...a}=e;return(0,i.yg)(u,(0,t.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"700"},"7.0.0"),(0,i.yg)("h3",{id:"breaking-changes"},"Breaking Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#664 Replaces ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/thecodingmachine/class-explorer"},"thecodingmachine/class-explorer")," with ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/alekitto/class-finder"},"kcs/class-finder")," resulting in the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory::setClassNameMapper")," being renamed to ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory::setFinder"),". This now expects an instance of ",(0,i.yg)("inlineCode",{parentName:"li"},"Kcs\\ClassFinder\\Finder")," instead of ",(0,i.yg)("inlineCode",{parentName:"li"},"Kcs\\ClassFinder\\Finder\\FinderInterface"),". @fogrye")),(0,i.yg)("h3",{id:"new-features"},"New Features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#649 Adds support for ",(0,i.yg)("inlineCode",{parentName:"li"},"subscription")," operations. @oojacoboo"),(0,i.yg)("li",{parentName:"ul"},"#612 Automatic query complexity analysis. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#611 Automatic persisted queries. @oprypkhantc")),(0,i.yg)("h3",{id:"improvements"},"Improvements"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#658 Improves on prefetching for nested fields. @grynchuk"),(0,i.yg)("li",{parentName:"ul"},"#646 Improves exception handling during schema parsing. @fogrye"),(0,i.yg)("li",{parentName:"ul"},"#636 Allows the use of middleware on construtor params/fields. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#623 Improves support for description arguments on types/fields. @downace"),(0,i.yg)("li",{parentName:"ul"},"#628 Properly handles ",(0,i.yg)("inlineCode",{parentName:"li"},"@param")," annotations for generics support on field annotated constructor arguments. @oojacoboo"),(0,i.yg)("li",{parentName:"ul"},"#584 Immutability improvements across the codebase. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#588 Prefetch improvements. @oprpkhantc"),(0,i.yg)("li",{parentName:"ul"},"#606 Adds support for phpdoc descriptions and deprecation annotations on native enums. @mdoelker"),(0,i.yg)("li",{parentName:"ul"},"Thanks to @shish, @cvergne and @mshapovalov for updating the docs!")),(0,i.yg)("h3",{id:"minor-changes"},"Minor Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#639 Added support for Symfony 7. @janatjak")),(0,i.yg)("h2",{id:"623"},"6.2.3"),(0,i.yg)("p",null,"Adds support for ",(0,i.yg)("inlineCode",{parentName:"p"},"Psr\\Container")," 1.1 with #601"),(0,i.yg)("h2",{id:"622"},"6.2.2"),(0,i.yg)("p",null,"This is a very simple release. We support Doctrine annotation 1.x and we've deprecated ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory::setDoctrineAnnotationReader")," in favor of native PHP attributes."),(0,i.yg)("h2",{id:"621"},"6.2.1"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Added support for new ",(0,i.yg)("inlineCode",{parentName:"li"},"Void")," return types, allowing use of ",(0,i.yg)("inlineCode",{parentName:"li"},"void")," from operation resolvers. #574"),(0,i.yg)("li",{parentName:"ul"},"Improvements with authorization middleware #571"),(0,i.yg)("li",{parentName:"ul"},"Updated vendor dependencies: #580 #558")),(0,i.yg)("h2",{id:"620"},"6.2.0"),(0,i.yg)("p",null,"Lots of little nuggets in this release! We're now targeting PHP ^8.1 and have testing on 8.2."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Better support for union types and enums: #530, #535, #561, #570"),(0,i.yg)("li",{parentName:"ul"},"Various bug and interface fixes: #532, #575, #564"),(0,i.yg)("li",{parentName:"ul"},"GraphQL v15 required: #542"),(0,i.yg)("li",{parentName:"ul"},"Lots of codebase improvements, more strict typing: #548")),(0,i.yg)("p",null,"A special thanks to @rusted-love and @oprypkhantc for their contributions."),(0,i.yg)("h2",{id:"610"},"6.1.0"),(0,i.yg)("p",null,"A shoutout to @bladl for his work on this release, improving the code for better typing and PHP 8.0 syntax updates!"),(0,i.yg)("h3",{id:"breaking-changes-1"},"Breaking Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#518 PSR-11 support now requires version 2"),(0,i.yg)("li",{parentName:"ul"},"#508 Due to some of the code improvements, additional typing has been added to some interfaces/classes. For instance, ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface::toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface::toGraphQLInputType")," now have the following signatures:")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"}," /**\n * @param (OutputType&GraphQLType)|null $subType\n *\n * @return OutputType&GraphQLType\n */\n public function toGraphQLOutputType(\n Type $type,\n OutputType|null $subType,\n ReflectionMethod|ReflectionProperty $reflector,\n DocBlock $docBlockObj\n ): OutputType;\n\n /**\n * @param (InputType&GraphQLType)|null $subType\n *\n * @return InputType&GraphQLType\n */\n public function toGraphQLInputType(\n Type $type,\n InputType|null $subType,\n string $argumentName,\n ReflectionMethod|ReflectionProperty $reflector,\n DocBlock $docBlockObj\n ): InputType;\n")),(0,i.yg)("h3",{id:"improvements-1"},"Improvements"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#510"),(0,i.yg)("li",{parentName:"ul"},"#508")),(0,i.yg)("h2",{id:"500"},"5.0.0"),(0,i.yg)("h3",{id:"dependencies"},"Dependencies"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Upgraded to using version 14.9 of ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/webonyx/graphql-php"},"webonyx/graphql-php"))),(0,i.yg)("h2",{id:"430"},"4.3.0"),(0,i.yg)("h3",{id:"breaking-change"},"Breaking change"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The method ",(0,i.yg)("inlineCode",{parentName:"li"},"setAnnotationCacheDir($directory)")," has been removed from the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),". The annotation\ncache will use your ",(0,i.yg)("inlineCode",{parentName:"li"},"Psr\\SimpleCache\\CacheInterface")," compliant cache handler set through the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),"\nconstructor.")),(0,i.yg)("h3",{id:"minor-changes-1"},"Minor changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Removed dependency for doctrine/cache and unified some of the cache layers following a PSR interface."),(0,i.yg)("li",{parentName:"ul"},"Cleaned up some of the documentation in an attempt to get things accurate with versioned releases.")),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h3",{id:"breaking-change-1"},"Breaking change"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h3",{id:"new-features-1"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"@Type")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/input-types#input-attribute"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Logged"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h3",{id:"breaking-change-2"},"Breaking change"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h3",{id:"new-features-2"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h3",{id:"minor-changes-2"},"Minor changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h3",{id:"miscellaneous"},"Miscellaneous"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h3",{id:"new-features-3"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h3",{id:"symfony"},"Symfony"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h3",{id:"laravel"},"Laravel"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h3",{id:"internals"},"Internals"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6053],{90333:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>r,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var t=a(58168),i=(a(96540),a(15680));a(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"version-7.0.0/changelog",title:"Changelog",description:"7.0.0",source:"@site/versioned_docs/version-7.0.0/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/CHANGELOG.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"docs",previous:{title:"Semantic versioning",permalink:"/docs/semver"}},p={},s=[{value:"7.0.0",id:"700",level:2},{value:"Breaking Changes",id:"breaking-changes",level:3},{value:"New Features",id:"new-features",level:3},{value:"Improvements",id:"improvements",level:3},{value:"Minor Changes",id:"minor-changes",level:3},{value:"6.2.3",id:"623",level:2},{value:"6.2.2",id:"622",level:2},{value:"6.2.1",id:"621",level:2},{value:"6.2.0",id:"620",level:2},{value:"6.1.0",id:"610",level:2},{value:"Breaking Changes",id:"breaking-changes-1",level:3},{value:"Improvements",id:"improvements-1",level:3},{value:"5.0.0",id:"500",level:2},{value:"Dependencies",id:"dependencies",level:3},{value:"4.3.0",id:"430",level:2},{value:"Breaking change",id:"breaking-change",level:3},{value:"Minor changes",id:"minor-changes-1",level:3},{value:"4.2.0",id:"420",level:2},{value:"Breaking change",id:"breaking-change-1",level:3},{value:"New features",id:"new-features-1",level:3},{value:"4.1.0",id:"410",level:2},{value:"Breaking change",id:"breaking-change-2",level:3},{value:"New features",id:"new-features-2",level:3},{value:"Minor changes",id:"minor-changes-2",level:3},{value:"Miscellaneous",id:"miscellaneous",level:3},{value:"4.0.0",id:"400",level:2},{value:"New features",id:"new-features-3",level:3},{value:"Symfony",id:"symfony",level:3},{value:"Laravel",id:"laravel",level:3},{value:"Internals",id:"internals",level:3}],d={toc:s},u="wrapper";function g(e){let{components:n,...a}=e;return(0,i.yg)(u,(0,t.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"700"},"7.0.0"),(0,i.yg)("h3",{id:"breaking-changes"},"Breaking Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#664 Replaces ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/thecodingmachine/class-explorer"},"thecodingmachine/class-explorer")," with ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/alekitto/class-finder"},"kcs/class-finder")," resulting in the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory::setClassNameMapper")," being renamed to ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory::setFinder"),". This now expects an instance of ",(0,i.yg)("inlineCode",{parentName:"li"},"Kcs\\ClassFinder\\Finder")," instead of ",(0,i.yg)("inlineCode",{parentName:"li"},"Kcs\\ClassFinder\\Finder\\FinderInterface"),". @fogrye")),(0,i.yg)("h3",{id:"new-features"},"New Features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#649 Adds support for ",(0,i.yg)("inlineCode",{parentName:"li"},"subscription")," operations. @oojacoboo"),(0,i.yg)("li",{parentName:"ul"},"#612 Automatic query complexity analysis. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#611 Automatic persisted queries. @oprypkhantc")),(0,i.yg)("h3",{id:"improvements"},"Improvements"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#658 Improves on prefetching for nested fields. @grynchuk"),(0,i.yg)("li",{parentName:"ul"},"#646 Improves exception handling during schema parsing. @fogrye"),(0,i.yg)("li",{parentName:"ul"},"#636 Allows the use of middleware on construtor params/fields. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#623 Improves support for description arguments on types/fields. @downace"),(0,i.yg)("li",{parentName:"ul"},"#628 Properly handles ",(0,i.yg)("inlineCode",{parentName:"li"},"@param")," annotations for generics support on field annotated constructor arguments. @oojacoboo"),(0,i.yg)("li",{parentName:"ul"},"#584 Immutability improvements across the codebase. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#588 Prefetch improvements. @oprpkhantc"),(0,i.yg)("li",{parentName:"ul"},"#606 Adds support for phpdoc descriptions and deprecation annotations on native enums. @mdoelker"),(0,i.yg)("li",{parentName:"ul"},"Thanks to @shish, @cvergne and @mshapovalov for updating the docs!")),(0,i.yg)("h3",{id:"minor-changes"},"Minor Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#639 Added support for Symfony 7. @janatjak")),(0,i.yg)("h2",{id:"623"},"6.2.3"),(0,i.yg)("p",null,"Adds support for ",(0,i.yg)("inlineCode",{parentName:"p"},"Psr\\Container")," 1.1 with #601"),(0,i.yg)("h2",{id:"622"},"6.2.2"),(0,i.yg)("p",null,"This is a very simple release. We support Doctrine annotation 1.x and we've deprecated ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory::setDoctrineAnnotationReader")," in favor of native PHP attributes."),(0,i.yg)("h2",{id:"621"},"6.2.1"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Added support for new ",(0,i.yg)("inlineCode",{parentName:"li"},"Void")," return types, allowing use of ",(0,i.yg)("inlineCode",{parentName:"li"},"void")," from operation resolvers. #574"),(0,i.yg)("li",{parentName:"ul"},"Improvements with authorization middleware #571"),(0,i.yg)("li",{parentName:"ul"},"Updated vendor dependencies: #580 #558")),(0,i.yg)("h2",{id:"620"},"6.2.0"),(0,i.yg)("p",null,"Lots of little nuggets in this release! We're now targeting PHP ^8.1 and have testing on 8.2."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Better support for union types and enums: #530, #535, #561, #570"),(0,i.yg)("li",{parentName:"ul"},"Various bug and interface fixes: #532, #575, #564"),(0,i.yg)("li",{parentName:"ul"},"GraphQL v15 required: #542"),(0,i.yg)("li",{parentName:"ul"},"Lots of codebase improvements, more strict typing: #548")),(0,i.yg)("p",null,"A special thanks to @rusted-love and @oprypkhantc for their contributions."),(0,i.yg)("h2",{id:"610"},"6.1.0"),(0,i.yg)("p",null,"A shoutout to @bladl for his work on this release, improving the code for better typing and PHP 8.0 syntax updates!"),(0,i.yg)("h3",{id:"breaking-changes-1"},"Breaking Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#518 PSR-11 support now requires version 2"),(0,i.yg)("li",{parentName:"ul"},"#508 Due to some of the code improvements, additional typing has been added to some interfaces/classes. For instance, ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface::toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface::toGraphQLInputType")," now have the following signatures:")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"}," /**\n * @param (OutputType&GraphQLType)|null $subType\n *\n * @return OutputType&GraphQLType\n */\n public function toGraphQLOutputType(\n Type $type,\n OutputType|null $subType,\n ReflectionMethod|ReflectionProperty $reflector,\n DocBlock $docBlockObj\n ): OutputType;\n\n /**\n * @param (InputType&GraphQLType)|null $subType\n *\n * @return InputType&GraphQLType\n */\n public function toGraphQLInputType(\n Type $type,\n InputType|null $subType,\n string $argumentName,\n ReflectionMethod|ReflectionProperty $reflector,\n DocBlock $docBlockObj\n ): InputType;\n")),(0,i.yg)("h3",{id:"improvements-1"},"Improvements"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#510"),(0,i.yg)("li",{parentName:"ul"},"#508")),(0,i.yg)("h2",{id:"500"},"5.0.0"),(0,i.yg)("h3",{id:"dependencies"},"Dependencies"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Upgraded to using version 14.9 of ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/webonyx/graphql-php"},"webonyx/graphql-php"))),(0,i.yg)("h2",{id:"430"},"4.3.0"),(0,i.yg)("h3",{id:"breaking-change"},"Breaking change"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The method ",(0,i.yg)("inlineCode",{parentName:"li"},"setAnnotationCacheDir($directory)")," has been removed from the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),". The annotation\ncache will use your ",(0,i.yg)("inlineCode",{parentName:"li"},"Psr\\SimpleCache\\CacheInterface")," compliant cache handler set through the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),"\nconstructor.")),(0,i.yg)("h3",{id:"minor-changes-1"},"Minor changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Removed dependency for doctrine/cache and unified some of the cache layers following a PSR interface."),(0,i.yg)("li",{parentName:"ul"},"Cleaned up some of the documentation in an attempt to get things accurate with versioned releases.")),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h3",{id:"breaking-change-1"},"Breaking change"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h3",{id:"new-features-1"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"@Type")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/input-types#input-attribute"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Logged"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h3",{id:"breaking-change-2"},"Breaking change"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h3",{id:"new-features-2"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h3",{id:"minor-changes-2"},"Minor changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h3",{id:"miscellaneous"},"Miscellaneous"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h3",{id:"new-features-3"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h3",{id:"symfony"},"Symfony"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h3",{id:"laravel"},"Laravel"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h3",{id:"internals"},"Internals"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/18100524.c086b122.js b/assets/js/18100524.e30015f9.js similarity index 99% rename from assets/js/18100524.c086b122.js rename to assets/js/18100524.e30015f9.js index 4f0409130f..09a2513c3c 100644 --- a/assets/js/18100524.c086b122.js +++ b/assets/js/18100524.e30015f9.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1264],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),u=a(57485),s=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[u,s]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=u??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),h(e)}),[s,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:u,tabValues:s}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=s[a].value;n!==l&&(y(t),u(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},58164:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>u,default:()=>m,frontMatter:()=>l,metadata:()=>s,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},u=void 0,s={unversionedId:"custom-types",id:"version-3.0/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-3.0/custom_types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/3.0/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/custom_types.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): OutputType;\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1264],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),u=a(57485),s=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[u,s]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=u??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),h(e)}),[s,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:u,tabValues:s}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=s[a].value;n!==l&&(y(t),u(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},58164:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>u,default:()=>m,frontMatter:()=>l,metadata:()=>s,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},u=void 0,s={unversionedId:"custom-types",id:"version-3.0/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-3.0/custom_types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/3.0/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/custom_types.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): OutputType;\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1891fd2b.646738f3.js b/assets/js/1891fd2b.b4b19628.js similarity index 99% rename from assets/js/1891fd2b.646738f3.js rename to assets/js/1891fd2b.b4b19628.js index e42ab76b24..9bce5c74ec 100644 --- a/assets/js/1891fd2b.646738f3.js +++ b/assets/js/1891fd2b.b4b19628.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3460],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[s,u]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=s??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),h(e)}),[u,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=u[a].value;n!==l&&(y(t),s(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},14716:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},s=void 0,u={unversionedId:"custom-types",id:"version-5.0/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-5.0/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/5.0/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/custom-types.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"version-5.0/docs",previous:{title:"Pagination",permalink:"/docs/5.0/pagination"},next:{title:"Custom annotations",permalink:"/docs/5.0/field-middlewares"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3460],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[s,u]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=s??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),h(e)}),[u,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=u[a].value;n!==l&&(y(t),s(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},14716:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},s=void 0,u={unversionedId:"custom-types",id:"version-5.0/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-5.0/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/5.0/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/custom-types.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"version-5.0/docs",previous:{title:"Pagination",permalink:"/docs/5.0/pagination"},next:{title:"Custom annotations",permalink:"/docs/5.0/field-middlewares"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/18d6c9c9.74a8728d.js b/assets/js/18d6c9c9.8e7d66c1.js similarity index 98% rename from assets/js/18d6c9c9.74a8728d.js rename to assets/js/18d6c9c9.8e7d66c1.js index e7c46f556d..7fcd8eb624 100644 --- a/assets/js/18d6c9c9.74a8728d.js +++ b/assets/js/18d6c9c9.8e7d66c1.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4422],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var r=n(96540),a=n(20053);const s={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(s.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var r=n(58168),a=n(96540),s=n(20053),o=n(23104),l=n(56347),i=n(57485),u=n(31682),c=n(89466);function d(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:r,default:a}}=e;return{value:t,label:n,attributes:r,default:a}}))}function h(e){const{values:t,children:n}=e;return(0,a.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function p(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const r=(0,l.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,a.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(r.location.search);t.set(s,e),r.replace({...r.location,search:t.toString()})}),[s,r])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:r}=e,s=h(e),[o,l]=(0,a.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!p({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const r=n.find((e=>e.default))??n[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:r}),[d,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[r,s]=(0,c.Dv)(n);return[r,(0,a.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:r}),b=(()=>{const e=i??d;return p({value:e,tabValues:s})?e:null})();(0,a.useLayoutEffect)((()=>{b&&l(b)}),[b]);return{selectedValue:o,selectValue:(0,a.useCallback)((e=>{if(!p({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function y(e){let{className:t,block:n,selectedValue:l,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),r=u[n].value;r!==l&&(d(t),i(r))},p=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:p,onClick:h},o,{className:(0,s.A)("tabs__item",g.tabItem,o?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:r}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==r}))))}function w(e){const t=f(e);return a.createElement("div",{className:(0,s.A)("tabs-container",g.tabList)},a.createElement(y,(0,r.A)({},e,t)),a.createElement(v,(0,r.A)({},e,t)))}function T(e){const t=(0,b.A)();return a.createElement(w,(0,r.A)({key:String(t)},e))}},61770:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>i,contentTitle:()=>o,default:()=>h,frontMatter:()=>s,metadata:()=>l,toc:()=>u});var r=n(58168),a=(n(96540),n(15680));n(67443),n(11470),n(19365);const s={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},o=void 0,l={unversionedId:"prefetch-method",id:"version-6.1/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-6.1/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/6.1/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/prefetch-method.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"docs",previous:{title:"Query plan",permalink:"/docs/6.1/query-plan"},next:{title:"File uploads",permalink:"/docs/6.1/file-uploads"}},i={},u=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],c={toc:u},d="wrapper";function h(e){let{components:t,...n}=e;return(0,a.yg)(d,(0,r.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("h2",{id:"the-problem"},"The problem"),(0,a.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,a.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,a.yg)("p",null,"A naive implementation will do this:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,a.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,a.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,a.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,a.yg)("a",{parentName:"p",href:"/docs/6.1/query-plan"},'"analyzing the query plan" documentation'),"."),(0,a.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,a.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,a.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')),(0,a.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,a.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,a.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,a.yg)("p",null,"For instance:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')),(0,a.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4422],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var r=n(96540),a=n(20053);const s={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(s.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var r=n(58168),a=n(96540),s=n(20053),o=n(23104),l=n(56347),i=n(57485),u=n(31682),c=n(89466);function d(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:r,default:a}}=e;return{value:t,label:n,attributes:r,default:a}}))}function h(e){const{values:t,children:n}=e;return(0,a.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function p(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const r=(0,l.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,a.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(r.location.search);t.set(s,e),r.replace({...r.location,search:t.toString()})}),[s,r])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:r}=e,s=h(e),[o,l]=(0,a.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!p({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const r=n.find((e=>e.default))??n[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:r}),[d,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[r,s]=(0,c.Dv)(n);return[r,(0,a.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:r}),b=(()=>{const e=i??d;return p({value:e,tabValues:s})?e:null})();(0,a.useLayoutEffect)((()=>{b&&l(b)}),[b]);return{selectedValue:o,selectValue:(0,a.useCallback)((e=>{if(!p({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function y(e){let{className:t,block:n,selectedValue:l,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),r=u[n].value;r!==l&&(d(t),i(r))},p=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:p,onClick:h},o,{className:(0,s.A)("tabs__item",g.tabItem,o?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:r}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==r}))))}function w(e){const t=f(e);return a.createElement("div",{className:(0,s.A)("tabs-container",g.tabList)},a.createElement(y,(0,r.A)({},e,t)),a.createElement(v,(0,r.A)({},e,t)))}function T(e){const t=(0,b.A)();return a.createElement(w,(0,r.A)({key:String(t)},e))}},61770:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>i,contentTitle:()=>o,default:()=>h,frontMatter:()=>s,metadata:()=>l,toc:()=>u});var r=n(58168),a=(n(96540),n(15680));n(67443),n(11470),n(19365);const s={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},o=void 0,l={unversionedId:"prefetch-method",id:"version-6.1/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-6.1/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/6.1/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/prefetch-method.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"docs",previous:{title:"Query plan",permalink:"/docs/6.1/query-plan"},next:{title:"File uploads",permalink:"/docs/6.1/file-uploads"}},i={},u=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],c={toc:u},d="wrapper";function h(e){let{components:t,...n}=e;return(0,a.yg)(d,(0,r.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("h2",{id:"the-problem"},"The problem"),(0,a.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,a.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,a.yg)("p",null,"A naive implementation will do this:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,a.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,a.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,a.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,a.yg)("a",{parentName:"p",href:"/docs/6.1/query-plan"},'"analyzing the query plan" documentation'),"."),(0,a.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,a.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,a.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')),(0,a.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,a.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,a.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,a.yg)("p",null,"For instance:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')),(0,a.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1a4e3797.0725399b.js b/assets/js/1a4e3797.0725399b.js deleted file mode 100644 index 2e7218da55..0000000000 --- a/assets/js/1a4e3797.0725399b.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! For license information please see 1a4e3797.0725399b.js.LICENSE.txt */ -(self.webpackChunk=self.webpackChunk||[]).push([[2138],{72733:e=>{function t(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function r(e){return"function"==typeof e}function n(e){return"object"==typeof e&&null!==e}function i(e){return void 0===e}e.exports=t,t.prototype._events=void 0,t.prototype._maxListeners=void 0,t.defaultMaxListeners=10,t.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},t.prototype.emit=function(e){var t,a,s,c,o,u;if(this._events||(this._events={}),"error"===e&&(!this._events.error||n(this._events.error)&&!this._events.error.length)){if((t=arguments[1])instanceof Error)throw t;var h=new Error('Uncaught, unspecified "error" event. ('+t+")");throw h.context=t,h}if(i(a=this._events[e]))return!1;if(r(a))switch(arguments.length){case 1:a.call(this);break;case 2:a.call(this,arguments[1]);break;case 3:a.call(this,arguments[1],arguments[2]);break;default:c=Array.prototype.slice.call(arguments,1),a.apply(this,c)}else if(n(a))for(c=Array.prototype.slice.call(arguments,1),s=(u=a.slice()).length,o=0;o0&&this._events[e].length>s&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace()),this},t.prototype.on=t.prototype.addListener,t.prototype.once=function(e,t){if(!r(t))throw TypeError("listener must be a function");var n=!1;function i(){this.removeListener(e,i),n||(n=!0,t.apply(this,arguments))}return i.listener=t,this.on(e,i),this},t.prototype.removeListener=function(e,t){var i,a,s,c;if(!r(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(s=(i=this._events[e]).length,a=-1,i===t||r(i.listener)&&i.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(n(i)){for(c=s;c-- >0;)if(i[c]===t||i[c].listener&&i[c].listener===t){a=c;break}if(a<0)return this;1===i.length?(i.length=0,delete this._events[e]):i.splice(a,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},t.prototype.removeAllListeners=function(e){var t,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(r(n=this._events[e]))this.removeListener(e,n);else if(n)for(;n.length;)this.removeListener(e,n[n.length-1]);return delete this._events[e],this},t.prototype.listeners=function(e){return this._events&&this._events[e]?r(this._events[e])?[this._events[e]]:this._events[e].slice():[]},t.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(r(t))return 1;if(t)return t.length}return 0},t.listenerCount=function(e,t){return e.listenerCount(t)}},74103:(e,t,r)=>{"use strict";var n=r(36571),i=r(19127),a=r(42223),s=r(33371),c=r(67691);function o(e,t,r,i){return new n(e,t,r,i)}o.version=r(16938),o.AlgoliaSearchHelper=n,o.SearchParameters=s,o.RecommendParameters=i,o.SearchResults=c,o.RecommendResults=a,e.exports=o},46732:(e,t,r)=>{"use strict";var n=r(72733);function i(e,t,r){this.main=e,this.fn=t,this.recommendFn=r,this.lastResults=null,this.lastRecommendResults=null}r(73014)(i,n),i.prototype.detach=function(){this.removeAllListeners(),this.main.detachDerivedHelper(this)},i.prototype.getModifiedState=function(e){return this.fn(e)},i.prototype.getModifiedRecommendState=function(e){return this.recommendFn(e)},e.exports=i},19127:e=>{"use strict";function t(e){e=e||{},this.params=e.params||[]}t.prototype={constructor:t,addParams:function(e){var r=this.params.slice();return r.push(e),new t({params:r})},removeParams:function(e){return new t({params:this.params.filter((function(t){return t.$$id!==e}))})},addFrequentlyBoughtTogether:function(e){return this.addParams(Object.assign({},e,{model:"bought-together"}))},addRelatedProducts:function(e){return this.addParams(Object.assign({},e,{model:"related-products"}))},addTrendingItems:function(e){return this.addParams(Object.assign({},e,{model:"trending-items"}))},addTrendingFacets:function(e){return this.addParams(Object.assign({},e,{model:"trending-facets"}))},addLookingSimilar:function(e){return this.addParams(Object.assign({},e,{model:"looking-similar"}))},_buildQueries:function(e,t){return this.params.filter((function(e){return void 0===t[e.$$id]})).map((function(t){var r=Object.assign({},t,{indexName:e});return delete r.$$id,r}))}},e.exports=t},42223:e=>{"use strict";function t(e,t){this._state=e,this._rawResults={};var r=this;e.params.forEach((function(e){var n=e.$$id;r[n]=t[n],r._rawResults[n]=t[n]}))}t.prototype={constructor:t},e.exports=t},44054:(e,t,r)=>{"use strict";var n=r(29110),i=r(40317),a=r(21383),s={addRefinement:function(e,t,r){if(s.isRefined(e,t,r))return e;var i=""+r,a=e[t]?e[t].concat(i):[i],c={};return c[t]=a,n({},c,e)},removeRefinement:function(e,t,r){if(void 0===r)return s.clearRefinement(e,(function(e,r){return t===r}));var n=""+r;return s.clearRefinement(e,(function(e,r){return t===r&&n===e}))},toggleRefinement:function(e,t,r){if(void 0===r)throw new Error("toggleRefinement should be used with a value");return s.isRefined(e,t,r)?s.removeRefinement(e,t,r):s.addRefinement(e,t,r)},clearRefinement:function(e,t,r){if(void 0===t)return i(e)?{}:e;if("string"==typeof t)return a(e,[t]);if("function"==typeof t){var n=!1,s=Object.keys(e).reduce((function(i,a){var s=e[a]||[],c=s.filter((function(e){return!t(e,a,r)}));return c.length!==s.length&&(n=!0),i[a]=c,i}),{});return n?s:e}},isRefined:function(e,t,r){var n=Boolean(e[t])&&e[t].length>0;if(void 0===r||!n)return n;var i=""+r;return-1!==e[t].indexOf(i)}};e.exports=s},33371:(e,t,r)=>{"use strict";var n=r(29110),i=r(20849),a=r(14843),s=r(44728),c=r(40317),o=r(21383),u=r(17507),h=r(72208),f=r(44054);function l(e,t){return Array.isArray(e)&&Array.isArray(t)?e.length===t.length&&e.every((function(e,r){return l(t[r],e)})):e===t}function m(e){var t=e?m._parseNumbers(e):{};void 0===t.userToken||h(t.userToken)||console.warn("[algoliasearch-helper] The `userToken` parameter is invalid. This can lead to wrong analytics.\n - Format: [a-zA-Z0-9_-]{1,64}"),this.facets=t.facets||[],this.disjunctiveFacets=t.disjunctiveFacets||[],this.hierarchicalFacets=t.hierarchicalFacets||[],this.facetsRefinements=t.facetsRefinements||{},this.facetsExcludes=t.facetsExcludes||{},this.disjunctiveFacetsRefinements=t.disjunctiveFacetsRefinements||{},this.numericRefinements=t.numericRefinements||{},this.tagRefinements=t.tagRefinements||[],this.hierarchicalFacetsRefinements=t.hierarchicalFacetsRefinements||{};var r=this;Object.keys(t).forEach((function(e){var n=-1!==m.PARAMETERS.indexOf(e),i=void 0!==t[e];!n&&i&&(r[e]=t[e])}))}m.PARAMETERS=Object.keys(new m),m._parseNumbers=function(e){if(e instanceof m)return e;var t={};if(["aroundPrecision","aroundRadius","getRankingInfo","minWordSizefor2Typos","minWordSizefor1Typo","page","maxValuesPerFacet","distinct","minimumAroundRadius","hitsPerPage","minProximity"].forEach((function(r){var n=e[r];if("string"==typeof n){var i=parseFloat(n);t[r]=isNaN(i)?n:i}})),Array.isArray(e.insideBoundingBox)&&(t.insideBoundingBox=e.insideBoundingBox.map((function(e){return Array.isArray(e)?e.map((function(e){return parseFloat(e)})):e}))),e.numericRefinements){var r={};Object.keys(e.numericRefinements).forEach((function(t){var n=e.numericRefinements[t]||{};r[t]={},Object.keys(n).forEach((function(e){var i=n[e].map((function(e){return Array.isArray(e)?e.map((function(e){return"string"==typeof e?parseFloat(e):e})):"string"==typeof e?parseFloat(e):e}));r[t][e]=i}))})),t.numericRefinements=r}return s(e,t)},m.make=function(e){var t=new m(e);return(e.hierarchicalFacets||[]).forEach((function(e){if(e.rootPath){var r=t.getHierarchicalRefinement(e.name);r.length>0&&0!==r[0].indexOf(e.rootPath)&&(t=t.clearRefinements(e.name)),0===(r=t.getHierarchicalRefinement(e.name)).length&&(t=t.toggleHierarchicalFacetRefinement(e.name,e.rootPath))}})),t},m.validate=function(e,t){var r=t||{};return e.tagFilters&&r.tagRefinements&&r.tagRefinements.length>0?new Error("[Tags] Cannot switch from the managed tag API to the advanced API. It is probably an error, if it is really what you want, you should first clear the tags with clearTags method."):e.tagRefinements.length>0&&r.tagFilters?new Error("[Tags] Cannot switch from the advanced tag API to the managed API. It is probably an error, if it is not, you should first clear the tags with clearTags method."):e.numericFilters&&r.numericRefinements&&c(r.numericRefinements)?new Error("[Numeric filters] Can't switch from the advanced to the managed API. It is probably an error, if this is really what you want, you have to first clear the numeric filters."):c(e.numericRefinements)&&r.numericFilters?new Error("[Numeric filters] Can't switch from the managed API to the advanced. It is probably an error, if this is really what you want, you have to first clear the numeric filters."):null},m.prototype={constructor:m,clearRefinements:function(e){var t={numericRefinements:this._clearNumericRefinements(e),facetsRefinements:f.clearRefinement(this.facetsRefinements,e,"conjunctiveFacet"),facetsExcludes:f.clearRefinement(this.facetsExcludes,e,"exclude"),disjunctiveFacetsRefinements:f.clearRefinement(this.disjunctiveFacetsRefinements,e,"disjunctiveFacet"),hierarchicalFacetsRefinements:f.clearRefinement(this.hierarchicalFacetsRefinements,e,"hierarchicalFacet")};return t.numericRefinements===this.numericRefinements&&t.facetsRefinements===this.facetsRefinements&&t.facetsExcludes===this.facetsExcludes&&t.disjunctiveFacetsRefinements===this.disjunctiveFacetsRefinements&&t.hierarchicalFacetsRefinements===this.hierarchicalFacetsRefinements?this:this.setQueryParameters(t)},clearTags:function(){return void 0===this.tagFilters&&0===this.tagRefinements.length?this:this.setQueryParameters({tagFilters:void 0,tagRefinements:[]})},setIndex:function(e){return e===this.index?this:this.setQueryParameters({index:e})},setQuery:function(e){return e===this.query?this:this.setQueryParameters({query:e})},setPage:function(e){return e===this.page?this:this.setQueryParameters({page:e})},setFacets:function(e){return this.setQueryParameters({facets:e})},setDisjunctiveFacets:function(e){return this.setQueryParameters({disjunctiveFacets:e})},setHitsPerPage:function(e){return this.hitsPerPage===e?this:this.setQueryParameters({hitsPerPage:e})},setTypoTolerance:function(e){return this.typoTolerance===e?this:this.setQueryParameters({typoTolerance:e})},addNumericRefinement:function(e,t,r){var n=u(r);if(this.isNumericRefined(e,t,n))return this;var i=s({},this.numericRefinements);return i[e]=s({},i[e]),i[e][t]?(i[e][t]=i[e][t].slice(),i[e][t].push(n)):i[e][t]=[n],this.setQueryParameters({numericRefinements:i})},getConjunctiveRefinements:function(e){return this.isConjunctiveFacet(e)&&this.facetsRefinements[e]||[]},getDisjunctiveRefinements:function(e){return this.isDisjunctiveFacet(e)&&this.disjunctiveFacetsRefinements[e]||[]},getHierarchicalRefinement:function(e){return this.hierarchicalFacetsRefinements[e]||[]},getExcludeRefinements:function(e){return this.isConjunctiveFacet(e)&&this.facetsExcludes[e]||[]},removeNumericRefinement:function(e,t,r){var n=r;return void 0!==n?this.isNumericRefined(e,t,n)?this.setQueryParameters({numericRefinements:this._clearNumericRefinements((function(r,i){return i===e&&r.op===t&&l(r.val,u(n))}))}):this:void 0!==t?this.isNumericRefined(e,t)?this.setQueryParameters({numericRefinements:this._clearNumericRefinements((function(r,n){return n===e&&r.op===t}))}):this:this.isNumericRefined(e)?this.setQueryParameters({numericRefinements:this._clearNumericRefinements((function(t,r){return r===e}))}):this},getNumericRefinements:function(e){return this.numericRefinements[e]||{}},getNumericRefinement:function(e,t){return this.numericRefinements[e]&&this.numericRefinements[e][t]},_clearNumericRefinements:function(e){if(void 0===e)return c(this.numericRefinements)?{}:this.numericRefinements;if("string"==typeof e)return o(this.numericRefinements,[e]);if("function"==typeof e){var t=!1,r=this.numericRefinements,n=Object.keys(r).reduce((function(n,i){var a=r[i],s={};return a=a||{},Object.keys(a).forEach((function(r){var n=a[r]||[],c=[];n.forEach((function(t){e({val:t,op:r},i,"numeric")||c.push(t)})),c.length!==n.length&&(t=!0),s[r]=c})),n[i]=s,n}),{});return t?n:this.numericRefinements}},addFacet:function(e){return this.isConjunctiveFacet(e)?this:this.setQueryParameters({facets:this.facets.concat([e])})},addDisjunctiveFacet:function(e){return this.isDisjunctiveFacet(e)?this:this.setQueryParameters({disjunctiveFacets:this.disjunctiveFacets.concat([e])})},addHierarchicalFacet:function(e){if(this.isHierarchicalFacet(e.name))throw new Error("Cannot declare two hierarchical facets with the same name: `"+e.name+"`");return this.setQueryParameters({hierarchicalFacets:this.hierarchicalFacets.concat([e])})},addFacetRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return f.isRefined(this.facetsRefinements,e,t)?this:this.setQueryParameters({facetsRefinements:f.addRefinement(this.facetsRefinements,e,t)})},addExcludeRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return f.isRefined(this.facetsExcludes,e,t)?this:this.setQueryParameters({facetsExcludes:f.addRefinement(this.facetsExcludes,e,t)})},addDisjunctiveFacetRefinement:function(e,t){if(!this.isDisjunctiveFacet(e))throw new Error(e+" is not defined in the disjunctiveFacets attribute of the helper configuration");return f.isRefined(this.disjunctiveFacetsRefinements,e,t)?this:this.setQueryParameters({disjunctiveFacetsRefinements:f.addRefinement(this.disjunctiveFacetsRefinements,e,t)})},addTagRefinement:function(e){if(this.isTagRefined(e))return this;var t={tagRefinements:this.tagRefinements.concat(e)};return this.setQueryParameters(t)},removeFacet:function(e){return this.isConjunctiveFacet(e)?this.clearRefinements(e).setQueryParameters({facets:this.facets.filter((function(t){return t!==e}))}):this},removeDisjunctiveFacet:function(e){return this.isDisjunctiveFacet(e)?this.clearRefinements(e).setQueryParameters({disjunctiveFacets:this.disjunctiveFacets.filter((function(t){return t!==e}))}):this},removeHierarchicalFacet:function(e){return this.isHierarchicalFacet(e)?this.clearRefinements(e).setQueryParameters({hierarchicalFacets:this.hierarchicalFacets.filter((function(t){return t.name!==e}))}):this},removeFacetRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return f.isRefined(this.facetsRefinements,e,t)?this.setQueryParameters({facetsRefinements:f.removeRefinement(this.facetsRefinements,e,t)}):this},removeExcludeRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return f.isRefined(this.facetsExcludes,e,t)?this.setQueryParameters({facetsExcludes:f.removeRefinement(this.facetsExcludes,e,t)}):this},removeDisjunctiveFacetRefinement:function(e,t){if(!this.isDisjunctiveFacet(e))throw new Error(e+" is not defined in the disjunctiveFacets attribute of the helper configuration");return f.isRefined(this.disjunctiveFacetsRefinements,e,t)?this.setQueryParameters({disjunctiveFacetsRefinements:f.removeRefinement(this.disjunctiveFacetsRefinements,e,t)}):this},removeTagRefinement:function(e){if(!this.isTagRefined(e))return this;var t={tagRefinements:this.tagRefinements.filter((function(t){return t!==e}))};return this.setQueryParameters(t)},toggleRefinement:function(e,t){return this.toggleFacetRefinement(e,t)},toggleFacetRefinement:function(e,t){if(this.isHierarchicalFacet(e))return this.toggleHierarchicalFacetRefinement(e,t);if(this.isConjunctiveFacet(e))return this.toggleConjunctiveFacetRefinement(e,t);if(this.isDisjunctiveFacet(e))return this.toggleDisjunctiveFacetRefinement(e,t);throw new Error("Cannot refine the undeclared facet "+e+"; it should be added to the helper options facets, disjunctiveFacets or hierarchicalFacets")},toggleConjunctiveFacetRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return this.setQueryParameters({facetsRefinements:f.toggleRefinement(this.facetsRefinements,e,t)})},toggleExcludeFacetRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return this.setQueryParameters({facetsExcludes:f.toggleRefinement(this.facetsExcludes,e,t)})},toggleDisjunctiveFacetRefinement:function(e,t){if(!this.isDisjunctiveFacet(e))throw new Error(e+" is not defined in the disjunctiveFacets attribute of the helper configuration");return this.setQueryParameters({disjunctiveFacetsRefinements:f.toggleRefinement(this.disjunctiveFacetsRefinements,e,t)})},toggleHierarchicalFacetRefinement:function(e,t){if(!this.isHierarchicalFacet(e))throw new Error(e+" is not defined in the hierarchicalFacets attribute of the helper configuration");var r=this._getHierarchicalFacetSeparator(this.getHierarchicalFacetByName(e)),i={};return void 0!==this.hierarchicalFacetsRefinements[e]&&this.hierarchicalFacetsRefinements[e].length>0&&(this.hierarchicalFacetsRefinements[e][0]===t||0===this.hierarchicalFacetsRefinements[e][0].indexOf(t+r))?-1===t.indexOf(r)?i[e]=[]:i[e]=[t.slice(0,t.lastIndexOf(r))]:i[e]=[t],this.setQueryParameters({hierarchicalFacetsRefinements:n({},i,this.hierarchicalFacetsRefinements)})},addHierarchicalFacetRefinement:function(e,t){if(this.isHierarchicalFacetRefined(e))throw new Error(e+" is already refined.");if(!this.isHierarchicalFacet(e))throw new Error(e+" is not defined in the hierarchicalFacets attribute of the helper configuration.");var r={};return r[e]=[t],this.setQueryParameters({hierarchicalFacetsRefinements:n({},r,this.hierarchicalFacetsRefinements)})},removeHierarchicalFacetRefinement:function(e){if(!this.isHierarchicalFacetRefined(e))return this;var t={};return t[e]=[],this.setQueryParameters({hierarchicalFacetsRefinements:n({},t,this.hierarchicalFacetsRefinements)})},toggleTagRefinement:function(e){return this.isTagRefined(e)?this.removeTagRefinement(e):this.addTagRefinement(e)},isDisjunctiveFacet:function(e){return this.disjunctiveFacets.indexOf(e)>-1},isHierarchicalFacet:function(e){return void 0!==this.getHierarchicalFacetByName(e)},isConjunctiveFacet:function(e){return this.facets.indexOf(e)>-1},isFacetRefined:function(e,t){return!!this.isConjunctiveFacet(e)&&f.isRefined(this.facetsRefinements,e,t)},isExcludeRefined:function(e,t){return!!this.isConjunctiveFacet(e)&&f.isRefined(this.facetsExcludes,e,t)},isDisjunctiveFacetRefined:function(e,t){return!!this.isDisjunctiveFacet(e)&&f.isRefined(this.disjunctiveFacetsRefinements,e,t)},isHierarchicalFacetRefined:function(e,t){if(!this.isHierarchicalFacet(e))return!1;var r=this.getHierarchicalRefinement(e);return t?-1!==r.indexOf(t):r.length>0},isNumericRefined:function(e,t,r){if(void 0===r&&void 0===t)return Boolean(this.numericRefinements[e]);var n=this.numericRefinements[e]&&void 0!==this.numericRefinements[e][t];if(void 0===r||!n)return n;var a,s,c=u(r),o=void 0!==(a=this.numericRefinements[e][t],s=c,i(a,(function(e){return l(e,s)})));return n&&o},isTagRefined:function(e){return-1!==this.tagRefinements.indexOf(e)},getRefinedDisjunctiveFacets:function(){var e=this,t=a(Object.keys(this.numericRefinements).filter((function(t){return Object.keys(e.numericRefinements[t]).length>0})),this.disjunctiveFacets);return Object.keys(this.disjunctiveFacetsRefinements).filter((function(t){return e.disjunctiveFacetsRefinements[t].length>0})).concat(t).concat(this.getRefinedHierarchicalFacets()).sort()},getRefinedHierarchicalFacets:function(){var e=this;return a(this.hierarchicalFacets.map((function(e){return e.name})),Object.keys(this.hierarchicalFacetsRefinements).filter((function(t){return e.hierarchicalFacetsRefinements[t].length>0}))).sort()},getUnrefinedDisjunctiveFacets:function(){var e=this.getRefinedDisjunctiveFacets();return this.disjunctiveFacets.filter((function(t){return-1===e.indexOf(t)}))},managedParameters:["index","facets","disjunctiveFacets","facetsRefinements","hierarchicalFacets","facetsExcludes","disjunctiveFacetsRefinements","numericRefinements","tagRefinements","hierarchicalFacetsRefinements"],getQueryParams:function(){var e=this.managedParameters,t={},r=this;return Object.keys(this).forEach((function(n){var i=r[n];-1===e.indexOf(n)&&void 0!==i&&(t[n]=i)})),t},setQueryParameter:function(e,t){if(this[e]===t)return this;var r={};return r[e]=t,this.setQueryParameters(r)},setQueryParameters:function(e){if(!e)return this;var t=m.validate(this,e);if(t)throw t;var r=this,n=m._parseNumbers(e),i=Object.keys(this).reduce((function(e,t){return e[t]=r[t],e}),{}),a=Object.keys(n).reduce((function(e,t){var r=void 0!==e[t],i=void 0!==n[t];return r&&!i?o(e,[t]):(i&&(e[t]=n[t]),e)}),i);return new this.constructor(a)},resetPage:function(){return void 0===this.page?this:this.setPage(0)},_getHierarchicalFacetSortBy:function(e){return e.sortBy||["isRefined:desc","name:asc"]},_getHierarchicalFacetSeparator:function(e){return e.separator||" > "},_getHierarchicalRootPath:function(e){return e.rootPath||null},_getHierarchicalShowParentLevel:function(e){return"boolean"!=typeof e.showParentLevel||e.showParentLevel},getHierarchicalFacetByName:function(e){return i(this.hierarchicalFacets,(function(t){return t.name===e}))},getHierarchicalFacetBreadcrumb:function(e){if(!this.isHierarchicalFacet(e))return[];var t=this.getHierarchicalRefinement(e)[0];if(!t)return[];var r=this._getHierarchicalFacetSeparator(this.getHierarchicalFacetByName(e));return t.split(r).map((function(e){return e.trim()}))},toString:function(){return JSON.stringify(this,null,2)}},e.exports=m},76673:(e,t,r)=>{"use strict";e.exports=function(e){return function(t,r){var n=e.hierarchicalFacets[r],u=e.hierarchicalFacetsRefinements[n.name]&&e.hierarchicalFacetsRefinements[n.name][0]||"",h=e._getHierarchicalFacetSeparator(n),f=e._getHierarchicalRootPath(n),l=e._getHierarchicalShowParentLevel(n),m=a(e._getHierarchicalFacetSortBy(n)),d=t.every((function(e){return e.exhaustive})),p=function(e,t,r,n,a){return function(u,h,f){var l=u;if(f>0){var m=0;for(l=u;m{"use strict";var n=r(78965),i=r(29110),a=r(2909),s=r(20849),c=r(43917),o=r(7577),u=r(44728),h=r(38601),f=a.escapeFacetValue,l=a.unescapeFacetValue,m=r(76673);function d(e){var t={};return e.forEach((function(e,r){t[e]=r})),t}function p(e,t,r){t&&t[r]&&(e.stats=t[r])}function g(e,t,r){var a=t[0];this._rawResults=t;var o=this;Object.keys(a).forEach((function(e){o[e]=a[e]}));var h=u({persistHierarchicalRootCount:!1},r);Object.keys(h).forEach((function(e){o[e]=h[e]})),this.processingTimeMS=t.reduce((function(e,t){return void 0===t.processingTimeMS?e:e+t.processingTimeMS}),0),this.disjunctiveFacets=[],this.hierarchicalFacets=e.hierarchicalFacets.map((function(){return[]})),this.facets=[];var f=e.getRefinedDisjunctiveFacets(),g=d(e.facets),v=d(e.disjunctiveFacets),y=1,R=a.facets||{};Object.keys(R).forEach((function(t){var r,n,i=R[t],u=(r=e.hierarchicalFacets,n=t,s(r,(function(e){return(e.attributes||[]).indexOf(n)>-1})));if(u){var h=u.attributes.indexOf(t),f=c(e.hierarchicalFacets,(function(e){return e.name===u.name}));o.hierarchicalFacets[f][h]={attribute:t,data:i,exhaustive:a.exhaustiveFacetsCount}}else{var l,m=-1!==e.disjunctiveFacets.indexOf(t),d=-1!==e.facets.indexOf(t);m&&(l=v[t],o.disjunctiveFacets[l]={name:t,data:i,exhaustive:a.exhaustiveFacetsCount},p(o.disjunctiveFacets[l],a.facets_stats,t)),d&&(l=g[t],o.facets[l]={name:t,data:i,exhaustive:a.exhaustiveFacetsCount},p(o.facets[l],a.facets_stats,t))}})),this.hierarchicalFacets=n(this.hierarchicalFacets),f.forEach((function(r){var n=t[y],s=n&&n.facets?n.facets:{},h=e.getHierarchicalFacetByName(r);Object.keys(s).forEach((function(t){var r,f=s[t];if(h){r=c(e.hierarchicalFacets,(function(e){return e.name===h.name}));var m=c(o.hierarchicalFacets[r],(function(e){return e.attribute===t}));if(-1===m)return;o.hierarchicalFacets[r][m].data=u({},o.hierarchicalFacets[r][m].data,f)}else{r=v[t];var d=a.facets&&a.facets[t]||{};o.disjunctiveFacets[r]={name:t,data:i({},f,d),exhaustive:n.exhaustiveFacetsCount},p(o.disjunctiveFacets[r],n.facets_stats,t),e.disjunctiveFacetsRefinements[t]&&e.disjunctiveFacetsRefinements[t].forEach((function(n){!o.disjunctiveFacets[r].data[n]&&e.disjunctiveFacetsRefinements[t].indexOf(l(n))>-1&&(o.disjunctiveFacets[r].data[n]=0)}))}})),y++})),e.getRefinedHierarchicalFacets().forEach((function(r){var n=e.getHierarchicalFacetByName(r),a=e._getHierarchicalFacetSeparator(n),s=e.getHierarchicalRefinement(r);0===s.length||s[0].split(a).length<2||t.slice(y).forEach((function(t){var r=t&&t.facets?t.facets:{};Object.keys(r).forEach((function(t){var u=r[t],h=c(e.hierarchicalFacets,(function(e){return e.name===n.name})),f=c(o.hierarchicalFacets[h],(function(e){return e.attribute===t}));if(-1!==f){var l={};if(s.length>0&&!o.persistHierarchicalRootCount){var m=s[0].split(a)[0];l[m]=o.hierarchicalFacets[h][f].data[m]}o.hierarchicalFacets[h][f].data=i(l,u,o.hierarchicalFacets[h][f].data)}})),y++}))})),Object.keys(e.facetsExcludes).forEach((function(t){var r=e.facetsExcludes[t],n=g[t];o.facets[n]={name:t,data:R[t],exhaustive:a.exhaustiveFacetsCount},r.forEach((function(e){o.facets[n]=o.facets[n]||{name:t},o.facets[n].data=o.facets[n].data||{},o.facets[n].data[e]=0}))})),this.hierarchicalFacets=this.hierarchicalFacets.map(m(e)),this.facets=n(this.facets),this.disjunctiveFacets=n(this.disjunctiveFacets),this._state=e}function v(e,t){function r(e){return e.name===t}if(e._state.isConjunctiveFacet(t)){var n=s(e.facets,r);return n?Object.keys(n.data).map((function(r){var i=f(r);return{name:r,escapedValue:i,count:n.data[r],isRefined:e._state.isFacetRefined(t,i),isExcluded:e._state.isExcludeRefined(t,r)}})):[]}if(e._state.isDisjunctiveFacet(t)){var i=s(e.disjunctiveFacets,r);return i?Object.keys(i.data).map((function(r){var n=f(r);return{name:r,escapedValue:n,count:i.data[r],isRefined:e._state.isDisjunctiveFacetRefined(t,n)}})):[]}if(e._state.isHierarchicalFacet(t)){var a=s(e.hierarchicalFacets,r);if(!a)return a;var c=e._state.getHierarchicalFacetByName(t),o=e._state._getHierarchicalFacetSeparator(c),u=l(e._state.getHierarchicalRefinement(t)[0]||"");0===u.indexOf(c.rootPath)&&(u=u.replace(c.rootPath+o,""));var h=u.split(o);return h.unshift(t),y(a,h,0),a}}function y(e,t,r){e.isRefined=e.name===(t[r]&&t[r].trim()),e.data&&e.data.forEach((function(e){y(e,t,r+1)}))}function R(e,t,r,n){if(n=n||0,Array.isArray(t))return e(t,r[n]);if(!t.data||0===t.data.length)return t;var a=t.data.map((function(t){return R(e,t,r,n+1)})),s=e(a,r[n]);return i({data:s},t)}function F(e,t){var r=s(e,(function(e){return e.name===t}));return r&&r.stats}function b(e,t,r,n,i){var a=s(i,(function(e){return e.name===r})),c=a&&a.data&&a.data[n]?a.data[n]:0,o=a&&a.exhaustive||!1;return{type:t,attributeName:r,name:n,count:c,exhaustive:o}}g.prototype.getFacetByName=function(e){function t(t){return t.name===e}return s(this.facets,t)||s(this.disjunctiveFacets,t)||s(this.hierarchicalFacets,t)},g.DEFAULT_SORT=["isRefined:desc","count:desc","name:asc"],g.prototype.getFacetValues=function(e,t){var r=v(this,e);if(r){var n,a=i({},t,{sortBy:g.DEFAULT_SORT,facetOrdering:!(t&&t.sortBy)}),s=this;if(Array.isArray(r))n=[e];else n=s._state.getHierarchicalFacetByName(r.name).attributes;return R((function(e,t){if(a.facetOrdering){var r=function(e,t){return e.renderingContent&&e.renderingContent.facetOrdering&&e.renderingContent.facetOrdering.values&&e.renderingContent.facetOrdering.values[t]}(s,t);if(r)return function(e,t){var r=[],n=[],i=(t.order||[]).reduce((function(e,t,r){return e[t]=r,e}),{});e.forEach((function(e){var t=e.path||e.name;void 0!==i[t]?r[i[t]]=e:n.push(e)})),r=r.filter((function(e){return e}));var a,s=t.sortRemainingBy;return"hidden"===s?r:(a="alpha"===s?[["path","name"],["asc","asc"]]:[["count"],["desc"]],r.concat(h(n,a[0],a[1])))}(e,r)}if(Array.isArray(a.sortBy)){var n=o(a.sortBy,g.DEFAULT_SORT);return h(e,n[0],n[1])}if("function"==typeof a.sortBy)return function(e,t){return t.sort(e)}(a.sortBy,e);throw new Error("options.sortBy is optional but if defined it must be either an array of string (predicates) or a sorting function")}),r,n)}},g.prototype.getFacetStats=function(e){return this._state.isConjunctiveFacet(e)?F(this.facets,e):this._state.isDisjunctiveFacet(e)?F(this.disjunctiveFacets,e):void 0},g.prototype.getRefinements=function(){var e=this._state,t=this,r=[];return Object.keys(e.facetsRefinements).forEach((function(n){e.facetsRefinements[n].forEach((function(i){r.push(b(e,"facet",n,i,t.facets))}))})),Object.keys(e.facetsExcludes).forEach((function(n){e.facetsExcludes[n].forEach((function(i){r.push(b(e,"exclude",n,i,t.facets))}))})),Object.keys(e.disjunctiveFacetsRefinements).forEach((function(n){e.disjunctiveFacetsRefinements[n].forEach((function(i){r.push(b(e,"disjunctive",n,i,t.disjunctiveFacets))}))})),Object.keys(e.hierarchicalFacetsRefinements).forEach((function(n){e.hierarchicalFacetsRefinements[n].forEach((function(i){r.push(function(e,t,r,n){var i=e.getHierarchicalFacetByName(t),a=e._getHierarchicalFacetSeparator(i),c=r.split(a),o=s(n,(function(e){return e.name===t})),u=c.reduce((function(e,t){var r=e&&s(e.data,(function(e){return e.name===t}));return void 0!==r?r:e}),o),h=u&&u.count||0,f=u&&u.exhaustive||!1,l=u&&u.path||"";return{type:"hierarchical",attributeName:t,name:l,count:h,exhaustive:f}}(e,n,i,t.hierarchicalFacets))}))})),Object.keys(e.numericRefinements).forEach((function(t){var n=e.numericRefinements[t];Object.keys(n).forEach((function(e){n[e].forEach((function(n){r.push({type:"numeric",attributeName:t,name:n,numericValue:n,operator:e})}))}))})),e.tagRefinements.forEach((function(e){r.push({type:"tag",attributeName:"_tags",name:e})})),r},e.exports=g},36571:(e,t,r)=>{"use strict";var n=r(72733),i=r(46732),a=r(2909).escapeFacetValue,s=r(73014),c=r(44728),o=r(40317),u=r(21383),h=r(19127),f=r(42223),l=r(49228),m=r(33371),d=r(67691),p=r(57749),g=r(16938);function v(e,t,r,n){"function"==typeof e.addAlgoliaAgent&&e.addAlgoliaAgent("JS Helper ("+g+")"),this.setClient(e);var i=r||{};i.index=t,this.state=m.make(i),this.recommendState=new h({params:i.recommendState}),this.lastResults=null,this.lastRecommendResults=null,this._queryId=0,this._recommendQueryId=0,this._lastQueryIdReceived=-1,this._lastRecommendQueryIdReceived=-1,this.derivedHelpers=[],this._currentNbQueries=0,this._currentNbRecommendQueries=0,this._searchResultsOptions=n,this._recommendCache={}}function y(e){if(e<0)throw new Error("Page requested below 0.");return this._change({state:this.state.setPage(e),isPageReset:!1}),this}function R(){return this.state.page}s(v,n),v.prototype.search=function(){return this._search({onlyWithDerivedHelpers:!1}),this},v.prototype.searchOnlyWithDerivedHelpers=function(){return this._search({onlyWithDerivedHelpers:!0}),this},v.prototype.recommend=function(){return this._recommend(),this},v.prototype.getQuery=function(){var e=this.state;return l._getHitsSearchParams(e)},v.prototype.searchOnce=function(e,t){var r=e?this.state.setQueryParameters(e):this.state,n=l._getQueries(r.index,r),i=this;if(this._currentNbQueries++,this.emit("searchOnce",{state:r}),!t)return this.client.search(n).then((function(e){return i._currentNbQueries--,0===i._currentNbQueries&&i.emit("searchQueueEmpty"),{content:new d(r,e.results),state:r,_originalResponse:e}}),(function(e){throw i._currentNbQueries--,0===i._currentNbQueries&&i.emit("searchQueueEmpty"),e}));this.client.search(n).then((function(e){i._currentNbQueries--,0===i._currentNbQueries&&i.emit("searchQueueEmpty"),t(null,new d(r,e.results),r)})).catch((function(e){i._currentNbQueries--,0===i._currentNbQueries&&i.emit("searchQueueEmpty"),t(e,null,r)}))},v.prototype.findAnswers=function(e){console.warn("[algoliasearch-helper] answers is no longer supported");var t=this.state,r=this.derivedHelpers[0];if(!r)return Promise.resolve([]);var n=r.getModifiedState(t),i=c({attributesForPrediction:e.attributesForPrediction,nbHits:e.nbHits},{params:u(l._getHitsSearchParams(n),["attributesToSnippet","hitsPerPage","restrictSearchableAttributes","snippetEllipsisText"])}),a="search for answers was called, but this client does not have a function client.initIndex(index).findAnswers";if("function"!=typeof this.client.initIndex)throw new Error(a);var s=this.client.initIndex(n.index);if("function"!=typeof s.findAnswers)throw new Error(a);return s.findAnswers(n.query,e.queryLanguages,i)},v.prototype.searchForFacetValues=function(e,t,r,n){var i="function"==typeof this.client.searchForFacetValues,s="function"==typeof this.client.initIndex;if(!i&&!s&&"function"!=typeof this.client.search)throw new Error("search for facet values (searchable) was called, but this client does not have a function client.searchForFacetValues or client.initIndex(index).searchForFacetValues");var c=this.state.setQueryParameters(n||{}),o=c.isDisjunctiveFacet(e),u=l.getSearchForFacetQuery(e,t,r,c);this._currentNbQueries++;var h,f=this;return i?h=this.client.searchForFacetValues([{indexName:c.index,params:u}]):s?h=this.client.initIndex(c.index).searchForFacetValues(u):(delete u.facetName,h=this.client.search([{type:"facet",facet:e,indexName:c.index,params:u}]).then((function(e){return e.results[0]}))),this.emit("searchForFacetValues",{state:c,facet:e,query:t}),h.then((function(t){return f._currentNbQueries--,0===f._currentNbQueries&&f.emit("searchQueueEmpty"),(t=Array.isArray(t)?t[0]:t).facetHits.forEach((function(t){t.escapedValue=a(t.value),t.isRefined=o?c.isDisjunctiveFacetRefined(e,t.escapedValue):c.isFacetRefined(e,t.escapedValue)})),t}),(function(e){throw f._currentNbQueries--,0===f._currentNbQueries&&f.emit("searchQueueEmpty"),e}))},v.prototype.setQuery=function(e){return this._change({state:this.state.resetPage().setQuery(e),isPageReset:!0}),this},v.prototype.clearRefinements=function(e){return this._change({state:this.state.resetPage().clearRefinements(e),isPageReset:!0}),this},v.prototype.clearTags=function(){return this._change({state:this.state.resetPage().clearTags(),isPageReset:!0}),this},v.prototype.addDisjunctiveFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().addDisjunctiveFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.addDisjunctiveRefine=function(){return this.addDisjunctiveFacetRefinement.apply(this,arguments)},v.prototype.addHierarchicalFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().addHierarchicalFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.addNumericRefinement=function(e,t,r){return this._change({state:this.state.resetPage().addNumericRefinement(e,t,r),isPageReset:!0}),this},v.prototype.addFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().addFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.addRefine=function(){return this.addFacetRefinement.apply(this,arguments)},v.prototype.addFacetExclusion=function(e,t){return this._change({state:this.state.resetPage().addExcludeRefinement(e,t),isPageReset:!0}),this},v.prototype.addExclude=function(){return this.addFacetExclusion.apply(this,arguments)},v.prototype.addTag=function(e){return this._change({state:this.state.resetPage().addTagRefinement(e),isPageReset:!0}),this},v.prototype.addFrequentlyBoughtTogether=function(e){return this._recommendChange({state:this.recommendState.addFrequentlyBoughtTogether(e)}),this},v.prototype.addRelatedProducts=function(e){return this._recommendChange({state:this.recommendState.addRelatedProducts(e)}),this},v.prototype.addTrendingItems=function(e){return this._recommendChange({state:this.recommendState.addTrendingItems(e)}),this},v.prototype.addTrendingFacets=function(e){return this._recommendChange({state:this.recommendState.addTrendingFacets(e)}),this},v.prototype.addLookingSimilar=function(e){return this._recommendChange({state:this.recommendState.addLookingSimilar(e)}),this},v.prototype.removeNumericRefinement=function(e,t,r){return this._change({state:this.state.resetPage().removeNumericRefinement(e,t,r),isPageReset:!0}),this},v.prototype.removeDisjunctiveFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().removeDisjunctiveFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.removeDisjunctiveRefine=function(){return this.removeDisjunctiveFacetRefinement.apply(this,arguments)},v.prototype.removeHierarchicalFacetRefinement=function(e){return this._change({state:this.state.resetPage().removeHierarchicalFacetRefinement(e),isPageReset:!0}),this},v.prototype.removeFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().removeFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.removeRefine=function(){return this.removeFacetRefinement.apply(this,arguments)},v.prototype.removeFacetExclusion=function(e,t){return this._change({state:this.state.resetPage().removeExcludeRefinement(e,t),isPageReset:!0}),this},v.prototype.removeExclude=function(){return this.removeFacetExclusion.apply(this,arguments)},v.prototype.removeTag=function(e){return this._change({state:this.state.resetPage().removeTagRefinement(e),isPageReset:!0}),this},v.prototype.removeFrequentlyBoughtTogether=function(e){return this._recommendChange({state:this.recommendState.removeParams(e)}),this},v.prototype.removeRelatedProducts=function(e){return this._recommendChange({state:this.recommendState.removeParams(e)}),this},v.prototype.removeTrendingItems=function(e){return this._recommendChange({state:this.recommendState.removeParams(e)}),this},v.prototype.removeTrendingFacets=function(e){return this._recommendChange({state:this.recommendState.removeParams(e)}),this},v.prototype.removeLookingSimilar=function(e){return this._recommendChange({state:this.recommendState.removeParams(e)}),this},v.prototype.toggleFacetExclusion=function(e,t){return this._change({state:this.state.resetPage().toggleExcludeFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.toggleExclude=function(){return this.toggleFacetExclusion.apply(this,arguments)},v.prototype.toggleRefinement=function(e,t){return this.toggleFacetRefinement(e,t)},v.prototype.toggleFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().toggleFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.toggleRefine=function(){return this.toggleFacetRefinement.apply(this,arguments)},v.prototype.toggleTag=function(e){return this._change({state:this.state.resetPage().toggleTagRefinement(e),isPageReset:!0}),this},v.prototype.nextPage=function(){var e=this.state.page||0;return this.setPage(e+1)},v.prototype.previousPage=function(){var e=this.state.page||0;return this.setPage(e-1)},v.prototype.setCurrentPage=y,v.prototype.setPage=y,v.prototype.setIndex=function(e){return this._change({state:this.state.resetPage().setIndex(e),isPageReset:!0}),this},v.prototype.setQueryParameter=function(e,t){return this._change({state:this.state.resetPage().setQueryParameter(e,t),isPageReset:!0}),this},v.prototype.setState=function(e){return this._change({state:m.make(e),isPageReset:!1}),this},v.prototype.overrideStateWithoutTriggeringChangeEvent=function(e){return this.state=new m(e),this},v.prototype.hasRefinements=function(e){return!!o(this.state.getNumericRefinements(e))||(this.state.isConjunctiveFacet(e)?this.state.isFacetRefined(e):this.state.isDisjunctiveFacet(e)?this.state.isDisjunctiveFacetRefined(e):!!this.state.isHierarchicalFacet(e)&&this.state.isHierarchicalFacetRefined(e))},v.prototype.isExcluded=function(e,t){return this.state.isExcludeRefined(e,t)},v.prototype.isDisjunctiveRefined=function(e,t){return this.state.isDisjunctiveFacetRefined(e,t)},v.prototype.hasTag=function(e){return this.state.isTagRefined(e)},v.prototype.isTagRefined=function(){return this.hasTagRefinements.apply(this,arguments)},v.prototype.getIndex=function(){return this.state.index},v.prototype.getCurrentPage=R,v.prototype.getPage=R,v.prototype.getTags=function(){return this.state.tagRefinements},v.prototype.getRefinements=function(e){var t=[];if(this.state.isConjunctiveFacet(e))this.state.getConjunctiveRefinements(e).forEach((function(e){t.push({value:e,type:"conjunctive"})})),this.state.getExcludeRefinements(e).forEach((function(e){t.push({value:e,type:"exclude"})}));else if(this.state.isDisjunctiveFacet(e)){this.state.getDisjunctiveRefinements(e).forEach((function(e){t.push({value:e,type:"disjunctive"})}))}var r=this.state.getNumericRefinements(e);return Object.keys(r).forEach((function(e){var n=r[e];t.push({value:n,operator:e,type:"numeric"})})),t},v.prototype.getNumericRefinement=function(e,t){return this.state.getNumericRefinement(e,t)},v.prototype.getHierarchicalFacetBreadcrumb=function(e){return this.state.getHierarchicalFacetBreadcrumb(e)},v.prototype._search=function(e){var t=this.state,r=[],n=[];e.onlyWithDerivedHelpers||(n=l._getQueries(t.index,t),r.push({state:t,queriesCount:n.length,helper:this}),this.emit("search",{state:t,results:this.lastResults}));var i=this.derivedHelpers.map((function(e){var n=e.getModifiedState(t),i=n.index?l._getQueries(n.index,n):[];return r.push({state:n,queriesCount:i.length,helper:e}),e.emit("search",{state:n,results:e.lastResults}),i})),a=Array.prototype.concat.apply(n,i),s=this._queryId++;if(this._currentNbQueries++,!a.length)return Promise.resolve({results:[]}).then(this._dispatchAlgoliaResponse.bind(this,r,s));try{this.client.search(a).then(this._dispatchAlgoliaResponse.bind(this,r,s)).catch(this._dispatchAlgoliaError.bind(this,s))}catch(c){this.emit("error",{error:c})}},v.prototype._recommend=function(){var e=this.state,t=this.recommendState,r=this.getIndex(),n=[{state:t,index:r,helper:this}],i=t.params.map((function(e){return e.$$id}));this.emit("fetch",{recommend:{state:t,results:this.lastRecommendResults}});var a=this._recommendCache,s=this.derivedHelpers.map((function(t){var r=t.getModifiedState(e).index;if(!r)return[];var s=t.getModifiedRecommendState(new h);return n.push({state:s,index:r,helper:t}),i=Array.prototype.concat.apply(i,s.params.map((function(e){return e.$$id}))),t.emit("fetch",{recommend:{state:s,results:t.lastRecommendResults}}),s._buildQueries(r,a)})),c=Array.prototype.concat.apply(this.recommendState._buildQueries(r,a),s);if(0!==c.length)if(c.length>0&&void 0===this.client.getRecommendations)console.warn("Please update algoliasearch/lite to the latest version in order to use recommend widgets.");else{var o=this._recommendQueryId++;this._currentNbRecommendQueries++;try{this.client.getRecommendations(c).then(this._dispatchRecommendResponse.bind(this,o,n,i)).catch(this._dispatchRecommendError.bind(this,o))}catch(u){this.emit("error",{error:u})}}},v.prototype._dispatchAlgoliaResponse=function(e,t,r){var n=this;if(!(t0},v.prototype._change=function(e){var t=e.state,r=e.isPageReset;t!==this.state&&(this.state=t,this.emit("change",{state:this.state,results:this.lastResults,isPageReset:r}))},v.prototype._recommendChange=function(e){var t=e.state;t!==this.recommendState&&(this.recommendState=t,this.emit("recommend:change",{search:{results:this.lastResults,state:this.state},recommend:{results:this.lastRecommendResults,state:this.recommendState}}))},v.prototype.clearCache=function(){return this.client.clearCache&&this.client.clearCache(),this},v.prototype.setClient=function(e){return this.client===e||("function"==typeof e.addAlgoliaAgent&&e.addAlgoliaAgent("JS Helper ("+g+")"),this.client=e),this},v.prototype.getClient=function(){return this.client},v.prototype.derive=function(e,t){var r=new i(this,e,t);return this.derivedHelpers.push(r),r},v.prototype.detachDerivedHelper=function(e){var t=this.derivedHelpers.indexOf(e);if(-1===t)throw new Error("Derived helper already detached");this.derivedHelpers.splice(t,1)},v.prototype.hasPendingRequests=function(){return this._currentNbQueries>0},e.exports=v},78965:e=>{"use strict";e.exports=function(e){return Array.isArray(e)?e.filter(Boolean):[]}},29110:e=>{"use strict";e.exports=function(){return Array.prototype.slice.call(arguments).reduceRight((function(e,t){return Object.keys(Object(t)).forEach((function(r){void 0!==t[r]&&(void 0!==e[r]&&delete e[r],e[r]=t[r])})),e}),{})}},2909:e=>{"use strict";e.exports={escapeFacetValue:function(e){return"string"!=typeof e?e:String(e).replace(/^-/,"\\-")},unescapeFacetValue:function(e){return"string"!=typeof e?e:e.replace(/^\\-/,"-")}}},20849:e=>{"use strict";e.exports=function(e,t){if(Array.isArray(e))for(var r=0;r{"use strict";e.exports=function(e,t){if(!Array.isArray(e))return-1;for(var r=0;r{e.exports=function(e){return e.reduce((function(e,t){return e.concat(t)}),[])}},7577:(e,t,r)=>{"use strict";var n=r(20849);e.exports=function(e,t){var r=(t||[]).map((function(e){return e.split(":")}));return e.reduce((function(e,t){var i=t.split(":"),a=n(r,(function(e){return e[0]===i[0]}));return i.length>1||!a?(e[0].push(i[0]),e[1].push(i[1]),e):(e[0].push(a[0]),e[1].push(a[1]),e)}),[[],[]])}},73014:e=>{"use strict";e.exports=function(e,t){e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}},14843:e=>{"use strict";e.exports=function(e,t){return e.filter((function(r,n){return t.indexOf(r)>-1&&e.indexOf(r)===n}))}},44728:e=>{"use strict";function t(e){return"function"==typeof e||Array.isArray(e)||"[object Object]"===Object.prototype.toString.call(e)}function r(e,n){if(e===n)return e;for(var i in n)if(Object.prototype.hasOwnProperty.call(n,i)&&"__proto__"!==i&&"constructor"!==i){var a=n[i],s=e[i];void 0!==s&&void 0===a||(t(s)&&t(a)?e[i]=r(s,a):e[i]="object"==typeof(c=a)&&null!==c?r(Array.isArray(c)?[]:{},c):c)}var c;return e}e.exports=function(e){t(e)||(e={});for(var n=1,i=arguments.length;n{"use strict";e.exports=function(e){return e&&Object.keys(e).length>0}},21383:e=>{"use strict";e.exports=function(e,t){if(null===e)return{};var r,n,i={},a=Object.keys(e);for(n=0;n=0||(i[r]=e[r]);return i}},38601:e=>{"use strict";function t(e,t){if(e!==t){var r=void 0!==e,n=null===e,i=void 0!==t,a=null===t;if(!a&&e>t||n&&i||!r)return 1;if(!n&&e=n.length?a:"desc"===n[i]?-a:a}return e.index-r.index})),i.map((function(e){return e.value}))}},17507:e=>{"use strict";e.exports=function e(t){if("number"==typeof t)return t;if("string"==typeof t)return parseFloat(t);if(Array.isArray(t))return t.map(e);throw new Error("The value should be a number, a parsable string or an array of those.")}},49228:(e,t,r)=>{"use strict";var n=r(44728);function i(e){return Object.keys(e).sort().reduce((function(t,r){return t[r]=e[r],t}),{})}var a={_getQueries:function(e,t){var r=[];return r.push({indexName:e,params:a._getHitsSearchParams(t)}),t.getRefinedDisjunctiveFacets().forEach((function(n){r.push({indexName:e,params:a._getDisjunctiveFacetSearchParams(t,n)})})),t.getRefinedHierarchicalFacets().forEach((function(n){var i=t.getHierarchicalFacetByName(n),s=t.getHierarchicalRefinement(n),c=t._getHierarchicalFacetSeparator(i);if(s.length>0&&s[0].split(c).length>1){var o=s[0].split(c).slice(0,-1).reduce((function(e,t,r){return e.concat({attribute:i.attributes[r],value:0===r?t:[e[e.length-1].value,t].join(c)})}),[]);o.forEach((function(n,s){var c=a._getDisjunctiveFacetSearchParams(t,n.attribute,0===s);function u(e){return i.attributes.some((function(t){return t===e.split(":")[0]}))}var h=(c.facetFilters||[]).reduce((function(e,t){if(Array.isArray(t)){var r=t.filter((function(e){return!u(e)}));r.length>0&&e.push(r)}return"string"!=typeof t||u(t)||e.push(t),e}),[]),f=o[s-1];c.facetFilters=s>0?h.concat(f.attribute+":"+f.value):h.length>0?h:void 0,r.push({indexName:e,params:c})}))}})),r},_getHitsSearchParams:function(e){var t=e.facets.concat(e.disjunctiveFacets).concat(a._getHitsHierarchicalFacetsAttributes(e)).sort(),r=a._getFacetFilters(e),s=a._getNumericFilters(e),c=a._getTagFilters(e),o={facets:t.indexOf("*")>-1?["*"]:t,tagFilters:c};return r.length>0&&(o.facetFilters=r),s.length>0&&(o.numericFilters=s),i(n({},e.getQueryParams(),o))},_getDisjunctiveFacetSearchParams:function(e,t,r){var s=a._getFacetFilters(e,t,r),c=a._getNumericFilters(e,t),o=a._getTagFilters(e),u={hitsPerPage:0,page:0,analytics:!1,clickAnalytics:!1};o.length>0&&(u.tagFilters=o);var h=e.getHierarchicalFacetByName(t);return u.facets=h?a._getDisjunctiveHierarchicalFacetAttribute(e,h,r):t,c.length>0&&(u.numericFilters=c),s.length>0&&(u.facetFilters=s),i(n({},e.getQueryParams(),u))},_getNumericFilters:function(e,t){if(e.numericFilters)return e.numericFilters;var r=[];return Object.keys(e.numericRefinements).forEach((function(n){var i=e.numericRefinements[n]||{};Object.keys(i).forEach((function(e){var a=i[e]||[];t!==n&&a.forEach((function(t){if(Array.isArray(t)){var i=t.map((function(t){return n+e+t}));r.push(i)}else r.push(n+e+t)}))}))})),r},_getTagFilters:function(e){return e.tagFilters?e.tagFilters:e.tagRefinements.join(",")},_getFacetFilters:function(e,t,r){var n=[],i=e.facetsRefinements||{};Object.keys(i).sort().forEach((function(e){(i[e]||[]).slice().sort().forEach((function(t){n.push(e+":"+t)}))}));var a=e.facetsExcludes||{};Object.keys(a).sort().forEach((function(e){(a[e]||[]).sort().forEach((function(t){n.push(e+":-"+t)}))}));var s=e.disjunctiveFacetsRefinements||{};Object.keys(s).sort().forEach((function(e){var r=s[e]||[];if(e!==t&&r&&0!==r.length){var i=[];r.slice().sort().forEach((function(t){i.push(e+":"+t)})),n.push(i)}}));var c=e.hierarchicalFacetsRefinements||{};return Object.keys(c).sort().forEach((function(i){var a=(c[i]||[])[0];if(void 0!==a){var s,o,u=e.getHierarchicalFacetByName(i),h=e._getHierarchicalFacetSeparator(u),f=e._getHierarchicalRootPath(u);if(t===i){if(-1===a.indexOf(h)||!f&&!0===r||f&&f.split(h).length===a.split(h).length)return;f?(o=f.split(h).length-1,a=f):(o=a.split(h).length-2,a=a.slice(0,a.lastIndexOf(h))),s=u.attributes[o]}else o=a.split(h).length-1,s=u.attributes[o];s&&n.push([s+":"+a])}})),n},_getHitsHierarchicalFacetsAttributes:function(e){return e.hierarchicalFacets.reduce((function(t,r){var n=e.getHierarchicalRefinement(r.name)[0];if(!n)return t.push(r.attributes[0]),t;var i=e._getHierarchicalFacetSeparator(r),a=n.split(i).length,s=r.attributes.slice(0,a+1);return t.concat(s)}),[])},_getDisjunctiveHierarchicalFacetAttribute:function(e,t,r){var n=e._getHierarchicalFacetSeparator(t);if(!0===r){var i=e._getHierarchicalRootPath(t),a=0;return i&&(a=i.split(n).length),[t.attributes[a]]}var s=(e.getHierarchicalRefinement(t.name)[0]||"").split(n).length-1;return t.attributes.slice(0,s+1)},getSearchForFacetQuery:function(e,t,r,s){var c=s.isDisjunctiveFacet(e)?s.clearRefinements(e):s,o={facetQuery:t,facetName:e};return"number"==typeof r&&(o.maxFacetHits=r),i(n({},a._getHitsSearchParams(c),o))}};e.exports=a},72208:e=>{"use strict";e.exports=function(e){return null!==e&&/^[a-zA-Z0-9_-]{1,64}$/.test(e)}},57749:(e,t,r)=>{"use strict";var n=r(20849),i=r(38657);e.exports=function(e){var t={};return e.forEach((function(e){e.forEach((function(e,r){t[e.objectID]?t[e.objectID]={indexSum:t[e.objectID].indexSum+r,count:t[e.objectID].count+1}:t[e.objectID]={indexSum:r,count:1}}))})),function(e,t){var r=[];return Object.keys(e).forEach((function(n){e[n].count<2&&(e[n].indexSum+=100),r.push({objectID:n,avgOfIndices:e[n].indexSum/t})})),r.sort((function(e,t){return e.avgOfIndices>t.avgOfIndices?1:-1}))}(t,e.length).reduce((function(t,r){var a=n(i(e),(function(e){return e.objectID===r.objectID}));return a?t.concat(a):t}),[])}},16938:e=>{"use strict";e.exports="3.21.0"},83643:function(e){e.exports=function(){"use strict";function e(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function t(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function r(r){for(var n=1;n=0||(i[r]=e[r]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(i[r]=e[r])}return i}function i(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){if(Symbol.iterator in Object(e)||"[object Arguments]"===Object.prototype.toString.call(e)){var r=[],n=!0,i=!1,a=void 0;try{for(var s,c=e[Symbol.iterator]();!(n=(s=c.next()).done)&&(r.push(s.value),!t||r.length!==t);n=!0);}catch(e){i=!0,a=e}finally{try{n||null==c.return||c.return()}finally{if(i)throw a}}return r}}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}function a(e){return function(e){if(Array.isArray(e)){for(var t=0,r=new Array(e.length);t2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}};return Promise.resolve().then((function(){c();var t=JSON.stringify(e);return a()[t]})).then((function(e){return Promise.all([e?e.value:t(),void 0!==e])})).then((function(e){var t=i(e,2),n=t[0],a=t[1];return Promise.all([n,a||r.miss(n)])})).then((function(e){return i(e,1)[0]}))},set:function(e,t){return Promise.resolve().then((function(){var i=a();return i[JSON.stringify(e)]={timestamp:(new Date).getTime(),value:t},n().setItem(r,JSON.stringify(i)),t}))},delete:function(e){return Promise.resolve().then((function(){var t=a();delete t[JSON.stringify(e)],n().setItem(r,JSON.stringify(t))}))},clear:function(){return Promise.resolve().then((function(){n().removeItem(r)}))}}}function c(e){var t=a(e.caches),r=t.shift();return void 0===r?{get:function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}};return t().then((function(e){return Promise.all([e,r.miss(e)])})).then((function(e){return i(e,1)[0]}))},set:function(e,t){return Promise.resolve(t)},delete:function(e){return Promise.resolve()},clear:function(){return Promise.resolve()}}:{get:function(e,n){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}};return r.get(e,n,i).catch((function(){return c({caches:t}).get(e,n,i)}))},set:function(e,n){return r.set(e,n).catch((function(){return c({caches:t}).set(e,n)}))},delete:function(e){return r.delete(e).catch((function(){return c({caches:t}).delete(e)}))},clear:function(){return r.clear().catch((function(){return c({caches:t}).clear()}))}}}function o(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{serializable:!0},t={};return{get:function(r,n){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}},a=JSON.stringify(r);if(a in t)return Promise.resolve(e.serializable?JSON.parse(t[a]):t[a]);var s=n(),c=i&&i.miss||function(){return Promise.resolve()};return s.then((function(e){return c(e)})).then((function(){return s}))},set:function(r,n){return t[JSON.stringify(r)]=e.serializable?JSON.stringify(n):n,Promise.resolve(n)},delete:function(e){return delete t[JSON.stringify(e)],Promise.resolve()},clear:function(){return t={},Promise.resolve()}}}function u(e){for(var t=e.length-1;t>0;t--){var r=Math.floor(Math.random()*(t+1)),n=e[t];e[t]=e[r],e[r]=n}return e}function h(e,t){return t?(Object.keys(t).forEach((function(r){e[r]=t[r](e)})),e):e}function f(e){for(var t=arguments.length,r=new Array(t>1?t-1:0),n=1;n0?n:void 0,timeout:r.timeout||t,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var d={Read:1,Write:2,Any:3},p=1,g=2,v=3;function y(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:p;return r(r({},e),{},{status:t,lastUpdate:Date.now()})}function R(e){return"string"==typeof e?{protocol:"https",url:e,accept:d.Any}:{protocol:e.protocol||"https",url:e.url,accept:e.accept||d.Any}}var F="GET",b="POST";function _(e,t){return Promise.all(t.map((function(t){return e.get(t,(function(){return Promise.resolve(y(t))}))}))).then((function(e){var r=e.filter((function(e){return function(e){return e.status===p||Date.now()-e.lastUpdate>12e4}(e)})),n=e.filter((function(e){return function(e){return e.status===v&&Date.now()-e.lastUpdate<=12e4}(e)})),i=[].concat(a(r),a(n));return{getTimeout:function(e,t){return(0===n.length&&0===e?1:n.length+3+e)*t},statelessHosts:i.length>0?i.map((function(e){return R(e)})):t}}))}function P(e,t,n,i){var s=[],c=function(e,t){if(e.method!==F&&(void 0!==e.data||void 0!==t.data)){var n=Array.isArray(e.data)?e.data:r(r({},e.data),t.data);return JSON.stringify(n)}}(n,i),o=function(e,t){var n=r(r({},e.headers),t.headers),i={};return Object.keys(n).forEach((function(e){var t=n[e];i[e.toLowerCase()]=t})),i}(e,i),u=n.method,h=n.method!==F?{}:r(r({},n.data),i.data),f=r(r(r({"x-algolia-agent":e.userAgent.value},e.queryParameters),h),i.queryParameters),l=0,m=function t(r,a){var h=r.pop();if(void 0===h)throw{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:O(s)};var m={data:c,headers:o,method:u,url:E(h,n.path,f),connectTimeout:a(l,e.timeouts.connect),responseTimeout:a(l,i.timeout)},d=function(e){var t={request:m,response:e,host:h,triesLeft:r.length};return s.push(t),t},p={onSuccess:function(e){return function(e){try{return JSON.parse(e.content)}catch(t){throw function(e,t){return{name:"DeserializationError",message:e,response:t}}(t.message,e)}}(e)},onRetry:function(n){var i=d(n);return n.isTimedOut&&l++,Promise.all([e.logger.info("Retryable failure",w(i)),e.hostsCache.set(h,y(h,n.isTimedOut?v:g))]).then((function(){return t(r,a)}))},onFail:function(e){throw d(e),function(e,t){var r=e.content,n=e.status,i=r;try{i=JSON.parse(r).message}catch(e){}return function(e,t,r){return{name:"ApiError",message:e,status:t,transporterStackTrace:r}}(i,n,t)}(e,O(s))}};return e.requester.send(m).then((function(e){return function(e,t){return function(e){var t=e.status;return e.isTimedOut||function(e){var t=e.isTimedOut,r=e.status;return!t&&!~~r}(e)||2!=~~(t/100)&&4!=~~(t/100)}(e)?t.onRetry(e):2==~~(e.status/100)?t.onSuccess(e):t.onFail(e)}(e,p)}))};return _(e.hostsCache,t).then((function(e){return m(a(e.statelessHosts).reverse(),e.getTimeout)}))}function j(e){var t={value:"Algolia for JavaScript (".concat(e,")"),add:function(e){var r="; ".concat(e.segment).concat(void 0!==e.version?" (".concat(e.version,")"):"");return-1===t.value.indexOf(r)&&(t.value="".concat(t.value).concat(r)),t}};return t}function E(e,t,r){var n=x(r),i="".concat(e.protocol,"://").concat(e.url,"/").concat("/"===t.charAt(0)?t.substr(1):t);return n.length&&(i+="?".concat(n)),i}function x(e){return Object.keys(e).map((function(t){return f("%s=%s",t,(r=e[t],"[object Object]"===Object.prototype.toString.call(r)||"[object Array]"===Object.prototype.toString.call(r)?JSON.stringify(e[t]):e[t]));var r})).join("&")}function O(e){return e.map((function(e){return w(e)}))}function w(e){var t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return r(r({},e),{},{request:r(r({},e.request),{},{headers:r(r({},e.request.headers),t)})})}var S=function(e){var t=e.appId,n=function(e,t,r){var n={"x-algolia-api-key":r,"x-algolia-application-id":t};return{headers:function(){return e===l.WithinHeaders?n:{}},queryParameters:function(){return e===l.WithinQueryParameters?n:{}}}}(void 0!==e.authMode?e.authMode:l.WithinHeaders,t,e.apiKey),a=function(e){var t=e.hostsCache,r=e.logger,n=e.requester,a=e.requestsCache,s=e.responsesCache,c=e.timeouts,o=e.userAgent,u=e.hosts,h=e.queryParameters,f={hostsCache:t,logger:r,requester:n,requestsCache:a,responsesCache:s,timeouts:c,userAgent:o,headers:e.headers,queryParameters:h,hosts:u.map((function(e){return R(e)})),read:function(e,t){var r=m(t,f.timeouts.read),n=function(){return P(f,f.hosts.filter((function(e){return!!(e.accept&d.Read)})),e,r)};if(!0!==(void 0!==r.cacheable?r.cacheable:e.cacheable))return n();var a={request:e,mappedRequestOptions:r,transporter:{queryParameters:f.queryParameters,headers:f.headers}};return f.responsesCache.get(a,(function(){return f.requestsCache.get(a,(function(){return f.requestsCache.set(a,n()).then((function(e){return Promise.all([f.requestsCache.delete(a),e])}),(function(e){return Promise.all([f.requestsCache.delete(a),Promise.reject(e)])})).then((function(e){var t=i(e,2);return t[0],t[1]}))}))}),{miss:function(e){return f.responsesCache.set(a,e)}})},write:function(e,t){return P(f,f.hosts.filter((function(e){return!!(e.accept&d.Write)})),e,m(t,f.timeouts.write))}};return f}(r(r({hosts:[{url:"".concat(t,"-dsn.algolia.net"),accept:d.Read},{url:"".concat(t,".algolia.net"),accept:d.Write}].concat(u([{url:"".concat(t,"-1.algolianet.com")},{url:"".concat(t,"-2.algolianet.com")},{url:"".concat(t,"-3.algolianet.com")}]))},e),{},{headers:r(r(r({},n.headers()),{"content-type":"application/x-www-form-urlencoded"}),e.headers),queryParameters:r(r({},n.queryParameters()),e.queryParameters)}));return h({transporter:a,appId:t,addAlgoliaAgent:function(e,t){a.userAgent.add({segment:e,version:t})},clearCache:function(){return Promise.all([a.requestsCache.clear(),a.responsesCache.clear()]).then((function(){}))}},e.methods)},A=function(e){return function(t,r){return t.method===F?e.transporter.read(t,r):e.transporter.write(t,r)}},N=function(e){return function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return h({transporter:e.transporter,appId:e.appId,indexName:t},r.methods)}},T=function(e){return function(t,n){var i=t.map((function(e){return r(r({},e),{},{params:x(e.params||{})})}));return e.transporter.read({method:b,path:"1/indexes/*/queries",data:{requests:i},cacheable:!0},n)}},H=function(e){return function(t,i){return Promise.all(t.map((function(t){var a=t.params,s=a.facetName,c=a.facetQuery,o=n(a,["facetName","facetQuery"]);return N(e)(t.indexName,{methods:{searchForFacetValues:I}}).searchForFacetValues(s,c,r(r({},i),o))})))}},Q=function(e){return function(t,r,n){return e.transporter.read({method:b,path:f("1/answers/%s/prediction",e.indexName),data:{query:t,queryLanguages:r},cacheable:!0},n)}},C=function(e){return function(t,r){return e.transporter.read({method:b,path:f("1/indexes/%s/query",e.indexName),data:{query:t},cacheable:!0},r)}},I=function(e){return function(t,r,n){return e.transporter.read({method:b,path:f("1/indexes/%s/facets/%s/query",e.indexName,t),data:{facetQuery:r},cacheable:!0},n)}},k=1,D=2,q=3,L=function(e){return function(t,n){var i=t.map((function(e){return r(r({},e),{},{threshold:e.threshold||0})}));return e.transporter.read({method:b,path:"1/indexes/*/recommendations",data:{requests:i},cacheable:!0},n)}};function V(e,t,n){var i,a={appId:e,apiKey:t,timeouts:{connect:1,read:2,write:30},requester:{send:function(e){return new Promise((function(t){var r=new XMLHttpRequest;r.open(e.method,e.url,!0),Object.keys(e.headers).forEach((function(t){return r.setRequestHeader(t,e.headers[t])}));var n,i=function(e,n){return setTimeout((function(){r.abort(),t({status:0,content:n,isTimedOut:!0})}),1e3*e)},a=i(e.connectTimeout,"Connection timeout");r.onreadystatechange=function(){r.readyState>r.OPENED&&void 0===n&&(clearTimeout(a),n=i(e.responseTimeout,"Socket timeout"))},r.onerror=function(){0===r.status&&(clearTimeout(a),clearTimeout(n),t({content:r.responseText||"Network request failed",status:r.status,isTimedOut:!1}))},r.onload=function(){clearTimeout(a),clearTimeout(n),t({content:r.responseText,status:r.status,isTimedOut:!1})},r.send(e.data)}))}},logger:(i=q,{debug:function(e,t){return k>=i&&console.debug(e,t),Promise.resolve()},info:function(e,t){return D>=i&&console.info(e,t),Promise.resolve()},error:function(e,t){return console.error(e,t),Promise.resolve()}}),responsesCache:o(),requestsCache:o({serializable:!1}),hostsCache:c({caches:[s({key:"".concat("4.23.3","-").concat(e)}),o()]}),userAgent:j("4.23.3").add({segment:"Browser",version:"lite"}),authMode:l.WithinQueryParameters};return S(r(r(r({},a),n),{},{methods:{search:T,searchForFacetValues:H,multipleQueries:T,multipleSearchForFacetValues:H,customRequest:A,initIndex:function(e){return function(t){return N(e)(t,{methods:{search:C,searchForFacetValues:I,findAnswers:Q}})}},getRecommendations:L}}))}return V.version="4.23.3",V}()},29057:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>A});var n=r(96540),i=r(20053),a=r(74103),s=r.n(a),c=r(83643),o=r.n(c),u=r(38193),h=r(5260),f=r(75489),l=r(44070),m=r(44586);const d=["zero","one","two","few","many","other"];function p(e){return d.filter((t=>e.includes(t)))}const g={locale:"en",pluralForms:p(["one","other"]),select:e=>1===e?"one":"other"};function v(){const{i18n:{currentLocale:e}}=(0,m.A)();return(0,n.useMemo)((()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:p(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),g}}),[e])}function y(){const e=v();return{selectMessage:(t,r)=>function(e,t,r){const n=e.split("|");if(1===n.length)return n[0];n.length>r.pluralForms.length&&console.error(`For locale=${r.locale}, a maximum of ${r.pluralForms.length} plural forms are expected (${r.pluralForms.join(",")}), but the message contains ${n.length}: ${e}`);const i=r.select(t),a=r.pluralForms.indexOf(i);return n[Math.min(a,n.length-1)]}(r,t,e)}}var R=r(24255),F=r(89532),b=r(69024),_=r(20481),P=r(21312),j=r(38126),E=r(51062),x=r(78511);const O={searchQueryInput:"searchQueryInput_u2C7",searchVersionInput:"searchVersionInput_m0Ui",searchResultsColumn:"searchResultsColumn_JPFH",algoliaLogo:"algoliaLogo_rT1R",algoliaLogoPathFill:"algoliaLogoPathFill_WdUC",searchResultItem:"searchResultItem_Tv2o",searchResultItemHeading:"searchResultItemHeading_KbCB",searchResultItemPath:"searchResultItemPath_lhe1",searchResultItemSummary:"searchResultItemSummary_AEaO",searchQueryColumn:"searchQueryColumn_RTkw",searchVersionColumn:"searchVersionColumn_ypXd",searchLogoColumn:"searchLogoColumn_rJIA",loadingSpinner:"loadingSpinner_XVxU","loading-spin":"loading-spin_vzvp",loader:"loader_vvXV"};function w(e){let{docsSearchVersionsHelpers:t}=e;const r=Object.entries(t.allDocsData).filter((e=>{let[,t]=e;return t.versions.length>1}));return n.createElement("div",{className:(0,i.A)("col","col--3","padding-left--none",O.searchVersionColumn)},r.map((e=>{let[i,a]=e;const s=r.length>1?`${i}: `:"";return n.createElement("select",{key:i,onChange:e=>t.setSearchVersion(i,e.target.value),defaultValue:t.searchVersions[i],className:O.searchVersionInput},a.versions.map(((e,t)=>n.createElement("option",{key:t,label:`${s}${e.label}`,value:e.name}))))})))}function S(){const{i18n:{currentLocale:e}}=(0,m.A)(),{algolia:{appId:t,apiKey:r,indexName:a}}=(0,j.c)(),c=(0,E.C)(),d=function(){const{selectMessage:e}=y();return t=>e(t,(0,P.T)({id:"theme.SearchPage.documentsFound.plurals",description:'Pluralized label for "{count} documents found". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One document found|{count} documents found"},{count:t}))}(),p=function(){const e=(0,l.Gy)(),[t,r]=(0,n.useState)((()=>Object.entries(e).reduce(((e,t)=>{let[r,n]=t;return{...e,[r]:n.versions[0].name}}),{}))),i=Object.values(e).some((e=>e.versions.length>1));return{allDocsData:e,versioningEnabled:i,searchVersions:t,setSearchVersion:(e,t)=>r((r=>({...r,[e]:t})))}}(),[g,v]=(0,R.b)(),b={items:[],query:null,totalResults:null,totalPages:null,lastPage:null,hasMore:null,loading:null},[S,A]=(0,n.useReducer)(((e,t)=>{switch(t.type){case"reset":return b;case"loading":return{...e,loading:!0};case"update":return g!==t.value.query?e:{...t.value,items:0===t.value.lastPage?t.value.items:e.items.concat(t.value.items)};case"advance":{const t=e.totalPages>e.lastPage+1;return{...e,lastPage:t?e.lastPage+1:e.lastPage,hasMore:t}}default:return e}}),b),N=o()(t,r),T=s()(N,a,{hitsPerPage:15,advancedSyntax:!0,disjunctiveFacets:["language","docusaurus_tag"]});T.on("result",(e=>{let{results:{query:t,hits:r,page:n,nbHits:i,nbPages:a}}=e;if(""===t||!Array.isArray(r))return void A({type:"reset"});const s=e=>e.replace(/algolia-docsearch-suggestion--highlight/g,"search-result-match"),o=r.map((e=>{let{url:t,_highlightResult:{hierarchy:r},_snippetResult:n={}}=e;const i=Object.keys(r).map((e=>s(r[e].value)));return{title:i.pop(),url:c(t),summary:n.content?`${s(n.content.value)}...`:"",breadcrumbs:i}}));A({type:"update",value:{items:o,query:t,totalResults:i,totalPages:a,lastPage:n,hasMore:a>n+1,loading:!1}})}));const[H,Q]=(0,n.useState)(null),C=(0,n.useRef)(0),I=(0,n.useRef)(u.A.canUseIntersectionObserver&&new IntersectionObserver((e=>{const{isIntersecting:t,boundingClientRect:{y:r}}=e[0];t&&C.current>r&&A({type:"advance"}),C.current=r}),{threshold:1})),k=()=>g?(0,P.T)({id:"theme.SearchPage.existingResultsTitle",message:'Search results for "{query}"',description:"The search page title for non-empty query"},{query:g}):(0,P.T)({id:"theme.SearchPage.emptyResultsTitle",message:"Search the documentation",description:"The search page title for empty query"}),D=(0,F._q)((function(t){void 0===t&&(t=0),T.addDisjunctiveFacetRefinement("docusaurus_tag","default"),T.addDisjunctiveFacetRefinement("language",e),Object.entries(p.searchVersions).forEach((e=>{let[t,r]=e;T.addDisjunctiveFacetRefinement("docusaurus_tag",`docs-${t}-${r}`)})),T.setQuery(g).setPage(t).search()}));return(0,n.useEffect)((()=>{if(!H)return;const e=I.current;return e?(e.observe(H),()=>e.unobserve(H)):()=>!0}),[H]),(0,n.useEffect)((()=>{A({type:"reset"}),g&&(A({type:"loading"}),setTimeout((()=>{D()}),300))}),[g,p.searchVersions,D]),(0,n.useEffect)((()=>{S.lastPage&&0!==S.lastPage&&D(S.lastPage)}),[D,S.lastPage]),n.createElement(x.A,null,n.createElement(h.A,null,n.createElement("title",null,(0,_.s)(k())),n.createElement("meta",{property:"robots",content:"noindex, follow"})),n.createElement("div",{className:"container margin-vert--lg"},n.createElement("h1",null,k()),n.createElement("form",{className:"row",onSubmit:e=>e.preventDefault()},n.createElement("div",{className:(0,i.A)("col",O.searchQueryColumn,{"col--9":p.versioningEnabled,"col--12":!p.versioningEnabled})},n.createElement("input",{type:"search",name:"q",className:O.searchQueryInput,placeholder:(0,P.T)({id:"theme.SearchPage.inputPlaceholder",message:"Type your search here",description:"The placeholder for search page input"}),"aria-label":(0,P.T)({id:"theme.SearchPage.inputLabel",message:"Search",description:"The ARIA label for search page input"}),onChange:e=>v(e.target.value),value:g,autoComplete:"off",autoFocus:!0})),p.versioningEnabled&&n.createElement(w,{docsSearchVersionsHelpers:p})),n.createElement("div",{className:"row"},n.createElement("div",{className:(0,i.A)("col","col--8",O.searchResultsColumn)},!!S.totalResults&&d(S.totalResults)),n.createElement("div",{className:(0,i.A)("col","col--4","text--right",O.searchLogoColumn)},n.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://www.algolia.com/","aria-label":(0,P.T)({id:"theme.SearchPage.algoliaLabel",message:"Search by Algolia",description:"The ARIA label for Algolia mention"})},n.createElement("svg",{viewBox:"0 0 168 24",className:O.algoliaLogo},n.createElement("g",{fill:"none"},n.createElement("path",{className:O.algoliaLogoPathFill,d:"M120.925 18.804c-4.386.02-4.386-3.54-4.386-4.106l-.007-13.336 2.675-.424v13.254c0 .322 0 2.358 1.718 2.364v2.248zm-10.846-2.18c.821 0 1.43-.047 1.855-.129v-2.719a6.334 6.334 0 0 0-1.574-.199 5.7 5.7 0 0 0-.897.069 2.699 2.699 0 0 0-.814.24c-.24.116-.439.28-.582.491-.15.212-.219.335-.219.656 0 .628.219.991.616 1.23s.938.362 1.615.362zm-.233-9.7c.883 0 1.629.109 2.231.328.602.218 1.088.525 1.444.915.363.396.609.922.76 1.483.157.56.232 1.175.232 1.85v6.874a32.5 32.5 0 0 1-1.868.314c-.834.123-1.772.185-2.813.185-.69 0-1.327-.069-1.895-.198a4.001 4.001 0 0 1-1.471-.636 3.085 3.085 0 0 1-.951-1.134c-.226-.465-.343-1.12-.343-1.803 0-.656.13-1.073.384-1.525a3.24 3.24 0 0 1 1.047-1.106c.445-.287.95-.492 1.532-.615a8.8 8.8 0 0 1 1.82-.185 8.404 8.404 0 0 1 1.972.24v-.438c0-.307-.035-.6-.11-.874a1.88 1.88 0 0 0-.384-.73 1.784 1.784 0 0 0-.724-.493 3.164 3.164 0 0 0-1.143-.205c-.616 0-1.177.075-1.69.164a7.735 7.735 0 0 0-1.26.307l-.321-2.192c.335-.117.834-.233 1.478-.349a10.98 10.98 0 0 1 2.073-.178zm52.842 9.626c.822 0 1.43-.048 1.854-.13V13.7a6.347 6.347 0 0 0-1.574-.199c-.294 0-.595.021-.896.069a2.7 2.7 0 0 0-.814.24 1.46 1.46 0 0 0-.582.491c-.15.212-.218.335-.218.656 0 .628.218.991.615 1.23.404.245.938.362 1.615.362zm-.226-9.694c.883 0 1.629.108 2.231.327.602.219 1.088.526 1.444.915.355.39.609.923.759 1.483a6.8 6.8 0 0 1 .233 1.852v6.873c-.41.088-1.034.19-1.868.314-.834.123-1.772.184-2.813.184-.69 0-1.327-.068-1.895-.198a4.001 4.001 0 0 1-1.471-.635 3.085 3.085 0 0 1-.951-1.134c-.226-.465-.343-1.12-.343-1.804 0-.656.13-1.073.384-1.524.26-.45.608-.82 1.047-1.107.445-.286.95-.491 1.532-.614a8.803 8.803 0 0 1 2.751-.13c.329.034.671.096 1.04.185v-.437a3.3 3.3 0 0 0-.109-.875 1.873 1.873 0 0 0-.384-.731 1.784 1.784 0 0 0-.724-.492 3.165 3.165 0 0 0-1.143-.205c-.616 0-1.177.075-1.69.164a7.75 7.75 0 0 0-1.26.307l-.321-2.193c.335-.116.834-.232 1.478-.348a11.633 11.633 0 0 1 2.073-.177zm-8.034-1.271a1.626 1.626 0 0 1-1.628-1.62c0-.895.725-1.62 1.628-1.62.904 0 1.63.725 1.63 1.62 0 .895-.733 1.62-1.63 1.62zm1.348 13.22h-2.689V7.27l2.69-.423v11.956zm-4.714 0c-4.386.02-4.386-3.54-4.386-4.107l-.008-13.336 2.676-.424v13.254c0 .322 0 2.358 1.718 2.364v2.248zm-8.698-5.903c0-1.156-.253-2.119-.746-2.788-.493-.677-1.183-1.01-2.067-1.01-.882 0-1.574.333-2.065 1.01-.493.676-.733 1.632-.733 2.788 0 1.168.246 1.953.74 2.63.492.683 1.183 1.018 2.066 1.018.882 0 1.574-.342 2.067-1.019.492-.683.738-1.46.738-2.63zm2.737-.007c0 .902-.13 1.584-.397 2.33a5.52 5.52 0 0 1-1.128 1.906 4.986 4.986 0 0 1-1.752 1.223c-.685.286-1.739.45-2.265.45-.528-.006-1.574-.157-2.252-.45a5.096 5.096 0 0 1-1.744-1.223c-.487-.527-.863-1.162-1.137-1.906a6.345 6.345 0 0 1-.41-2.33c0-.902.123-1.77.397-2.508a5.554 5.554 0 0 1 1.15-1.892 5.133 5.133 0 0 1 1.75-1.216c.679-.287 1.425-.423 2.232-.423.808 0 1.553.142 2.237.423a4.88 4.88 0 0 1 1.753 1.216 5.644 5.644 0 0 1 1.135 1.892c.287.738.431 1.606.431 2.508zm-20.138 0c0 1.12.246 2.363.738 2.882.493.52 1.13.78 1.91.78.424 0 .828-.062 1.204-.178.377-.116.677-.253.917-.417V9.33a10.476 10.476 0 0 0-1.766-.226c-.971-.028-1.71.37-2.23 1.004-.513.636-.773 1.75-.773 2.788zm7.438 5.274c0 1.824-.466 3.156-1.404 4.004-.936.846-2.367 1.27-4.296 1.27-.705 0-2.17-.137-3.34-.396l.431-2.118c.98.205 2.272.26 2.95.26 1.074 0 1.84-.219 2.299-.656.459-.437.684-1.086.684-1.948v-.437a8.07 8.07 0 0 1-1.047.397c-.43.13-.93.198-1.492.198-.739 0-1.41-.116-2.018-.349a4.206 4.206 0 0 1-1.567-1.025c-.431-.45-.774-1.017-1.013-1.694-.24-.677-.363-1.885-.363-2.773 0-.834.13-1.88.384-2.577.26-.696.629-1.298 1.129-1.796.493-.498 1.095-.881 1.8-1.162a6.605 6.605 0 0 1 2.428-.457c.87 0 1.67.109 2.45.24.78.129 1.444.265 1.985.415V18.17zM6.972 6.677v1.627c-.712-.446-1.52-.67-2.425-.67-.585 0-1.045.13-1.38.391a1.24 1.24 0 0 0-.502 1.03c0 .425.164.765.494 1.02.33.256.835.532 1.516.83.447.192.795.356 1.045.495.25.138.537.332.862.582.324.25.563.548.718.894.154.345.23.741.23 1.188 0 .947-.334 1.691-1.004 2.234-.67.542-1.537.814-2.601.814-1.18 0-2.16-.229-2.936-.686v-1.708c.84.628 1.814.942 2.92.942.585 0 1.048-.136 1.388-.407.34-.271.51-.646.51-1.125 0-.287-.1-.55-.302-.79-.203-.24-.42-.42-.655-.542-.234-.123-.585-.29-1.053-.503a61.27 61.27 0 0 1-.582-.271 13.67 13.67 0 0 1-.55-.287 4.275 4.275 0 0 1-.567-.351 6.92 6.92 0 0 1-.455-.4c-.18-.17-.31-.34-.39-.51-.08-.17-.155-.37-.224-.598a2.553 2.553 0 0 1-.104-.742c0-.915.333-1.638.998-2.17.664-.532 1.523-.798 2.576-.798.968 0 1.793.17 2.473.51zm7.468 5.696v-.287c-.022-.607-.187-1.088-.495-1.444-.309-.357-.75-.535-1.324-.535-.532 0-.99.194-1.373.583-.382.388-.622.949-.717 1.683h3.909zm1.005 2.792v1.404c-.596.34-1.383.51-2.362.51-1.255 0-2.255-.377-3-1.132-.744-.755-1.116-1.744-1.116-2.968 0-1.297.34-2.316 1.021-3.055.68-.74 1.548-1.11 2.6-1.11 1.033 0 1.852.323 2.458.966.606.644.91 1.572.91 2.784 0 .33-.033.676-.096 1.038h-5.314c.107.702.405 1.239.894 1.611.49.372 1.106.558 1.85.558.862 0 1.58-.202 2.155-.606zm6.605-1.77h-1.212c-.596 0-1.045.116-1.349.35-.303.234-.454.532-.454.894 0 .372.117.664.35.877.235.213.575.32 1.022.32.51 0 .912-.142 1.204-.424.293-.281.44-.651.44-1.108v-.91zm-4.068-2.554V9.325c.627-.361 1.457-.542 2.489-.542 2.116 0 3.175 1.026 3.175 3.08V17h-1.548v-.957c-.415.68-1.143 1.02-2.186 1.02-.766 0-1.38-.22-1.843-.661-.462-.442-.694-1.003-.694-1.684 0-.776.293-1.38.878-1.81.585-.431 1.404-.647 2.457-.647h1.34V11.8c0-.554-.133-.971-.399-1.253-.266-.282-.707-.423-1.324-.423a4.07 4.07 0 0 0-2.345.718zm9.333-1.93v1.42c.394-1 1.101-1.5 2.123-1.5.148 0 .313.016.494.048v1.531a1.885 1.885 0 0 0-.75-.143c-.542 0-.989.24-1.34.718-.351.479-.527 1.048-.527 1.707V17h-1.563V8.91h1.563zm5.01 4.084c.022.82.272 1.492.75 2.019.479.526 1.15.79 2.01.79.639 0 1.235-.176 1.788-.527v1.404c-.521.319-1.186.479-1.995.479-1.265 0-2.276-.4-3.031-1.197-.755-.798-1.133-1.792-1.133-2.984 0-1.16.38-2.151 1.14-2.975.761-.825 1.79-1.237 3.088-1.237.702 0 1.346.149 1.93.447v1.436a3.242 3.242 0 0 0-1.77-.495c-.84 0-1.513.266-2.019.798-.505.532-.758 1.213-.758 2.042zM40.24 5.72v4.579c.458-1 1.293-1.5 2.505-1.5.787 0 1.42.245 1.899.734.479.49.718 1.17.718 2.042V17h-1.564v-5.106c0-.553-.14-.98-.422-1.284-.282-.303-.652-.455-1.11-.455-.531 0-1.002.202-1.411.606-.41.405-.615 1.022-.615 1.851V17h-1.563V5.72h1.563zm14.966 10.02c.596 0 1.096-.253 1.5-.758.404-.506.606-1.157.606-1.955 0-.915-.202-1.62-.606-2.114-.404-.495-.92-.742-1.548-.742-.553 0-1.05.224-1.491.67-.442.447-.662 1.133-.662 2.058 0 .958.212 1.67.638 2.138.425.469.946.703 1.563.703zM53.004 5.72v4.42c.574-.894 1.388-1.341 2.44-1.341 1.022 0 1.857.383 2.506 1.149.649.766.973 1.781.973 3.047 0 1.138-.309 2.109-.925 2.912-.617.803-1.463 1.205-2.537 1.205-1.075 0-1.894-.447-2.457-1.34V17h-1.58V5.72h1.58zm9.908 11.104l-3.223-7.913h1.739l1.005 2.632 1.26 3.415c.096-.32.48-1.458 1.15-3.415l.909-2.632h1.66l-2.92 7.866c-.777 2.074-1.963 3.11-3.559 3.11a2.92 2.92 0 0 1-.734-.079v-1.34c.17.042.351.064.543.064 1.032 0 1.755-.57 2.17-1.708z"}),n.createElement("path",{fill:"#5468FF",d:"M78.988.938h16.594a2.968 2.968 0 0 1 2.966 2.966V20.5a2.967 2.967 0 0 1-2.966 2.964H78.988a2.967 2.967 0 0 1-2.966-2.964V3.897A2.961 2.961 0 0 1 78.988.938z"}),n.createElement("path",{fill:"white",d:"M89.632 5.967v-.772a.978.978 0 0 0-.978-.977h-2.28a.978.978 0 0 0-.978.977v.793c0 .088.082.15.171.13a7.127 7.127 0 0 1 1.984-.28c.65 0 1.295.088 1.917.259.082.02.164-.04.164-.13m-6.248 1.01l-.39-.389a.977.977 0 0 0-1.382 0l-.465.465a.973.973 0 0 0 0 1.38l.383.383c.062.061.15.047.205-.014.226-.307.472-.601.746-.874.281-.28.568-.526.883-.751.068-.042.075-.137.02-.2m4.16 2.453v3.341c0 .096.104.165.192.117l2.97-1.537c.068-.034.089-.117.055-.184a3.695 3.695 0 0 0-3.08-1.866c-.068 0-.136.054-.136.13m0 8.048a4.489 4.489 0 0 1-4.49-4.482 4.488 4.488 0 0 1 4.49-4.482 4.488 4.488 0 0 1 4.489 4.482 4.484 4.484 0 0 1-4.49 4.482m0-10.85a6.363 6.363 0 1 0 0 12.729 6.37 6.37 0 0 0 6.372-6.368 6.358 6.358 0 0 0-6.371-6.36"})))))),S.items.length>0?n.createElement("main",null,S.items.map(((e,t)=>{let{title:r,url:a,summary:s,breadcrumbs:c}=e;return n.createElement("article",{key:t,className:O.searchResultItem},n.createElement("h2",{className:O.searchResultItemHeading},n.createElement(f.A,{to:a,dangerouslySetInnerHTML:{__html:r}})),c.length>0&&n.createElement("nav",{"aria-label":"breadcrumbs"},n.createElement("ul",{className:(0,i.A)("breadcrumbs",O.searchResultItemPath)},c.map(((e,t)=>n.createElement("li",{key:t,className:"breadcrumbs__item",dangerouslySetInnerHTML:{__html:e}}))))),s&&n.createElement("p",{className:O.searchResultItemSummary,dangerouslySetInnerHTML:{__html:s}}))}))):[g&&!S.loading&&n.createElement("p",{key:"no-results"},n.createElement(P.A,{id:"theme.SearchPage.noResultsText",description:"The paragraph for empty search result"},"No results were found")),!!S.loading&&n.createElement("div",{key:"spinner",className:O.loadingSpinner})],S.hasMore&&n.createElement("div",{className:O.loader,ref:Q},n.createElement(P.A,{id:"theme.SearchPage.fetchingNewResults",description:"The paragraph for fetching new search results"},"Fetching new results..."))))}function A(){return n.createElement(b.e3,{className:"search-page-wrapper"},n.createElement(S,null))}}}]); \ No newline at end of file diff --git a/assets/js/1a4e3797.0725399b.js.LICENSE.txt b/assets/js/1a4e3797.0725399b.js.LICENSE.txt deleted file mode 100644 index 92dc1757f2..0000000000 --- a/assets/js/1a4e3797.0725399b.js.LICENSE.txt +++ /dev/null @@ -1 +0,0 @@ -/*! algoliasearch-lite.umd.js | 4.23.3 | © Algolia, inc. | https://github.com/algolia/algoliasearch-client-javascript */ diff --git a/assets/js/1a4e3797.491d505b.js b/assets/js/1a4e3797.491d505b.js new file mode 100644 index 0000000000..63721359e5 --- /dev/null +++ b/assets/js/1a4e3797.491d505b.js @@ -0,0 +1,2 @@ +/*! For license information please see 1a4e3797.491d505b.js.LICENSE.txt */ +(self.webpackChunk=self.webpackChunk||[]).push([[2138],{72733:e=>{function t(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function r(e){return"function"==typeof e}function n(e){return"object"==typeof e&&null!==e}function i(e){return void 0===e}e.exports=t,t.prototype._events=void 0,t.prototype._maxListeners=void 0,t.defaultMaxListeners=10,t.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw TypeError("n must be a positive number");return this._maxListeners=e,this},t.prototype.emit=function(e){var t,a,s,c,o,u;if(this._events||(this._events={}),"error"===e&&(!this._events.error||n(this._events.error)&&!this._events.error.length)){if((t=arguments[1])instanceof Error)throw t;var h=new Error('Uncaught, unspecified "error" event. ('+t+")");throw h.context=t,h}if(i(a=this._events[e]))return!1;if(r(a))switch(arguments.length){case 1:a.call(this);break;case 2:a.call(this,arguments[1]);break;case 3:a.call(this,arguments[1],arguments[2]);break;default:c=Array.prototype.slice.call(arguments,1),a.apply(this,c)}else if(n(a))for(c=Array.prototype.slice.call(arguments,1),s=(u=a.slice()).length,o=0;o0&&this._events[e].length>s&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace()),this},t.prototype.on=t.prototype.addListener,t.prototype.once=function(e,t){if(!r(t))throw TypeError("listener must be a function");var n=!1;function i(){this.removeListener(e,i),n||(n=!0,t.apply(this,arguments))}return i.listener=t,this.on(e,i),this},t.prototype.removeListener=function(e,t){var i,a,s,c;if(!r(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(s=(i=this._events[e]).length,a=-1,i===t||r(i.listener)&&i.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(n(i)){for(c=s;c-- >0;)if(i[c]===t||i[c].listener&&i[c].listener===t){a=c;break}if(a<0)return this;1===i.length?(i.length=0,delete this._events[e]):i.splice(a,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},t.prototype.removeAllListeners=function(e){var t,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(r(n=this._events[e]))this.removeListener(e,n);else if(n)for(;n.length;)this.removeListener(e,n[n.length-1]);return delete this._events[e],this},t.prototype.listeners=function(e){return this._events&&this._events[e]?r(this._events[e])?[this._events[e]]:this._events[e].slice():[]},t.prototype.listenerCount=function(e){if(this._events){var t=this._events[e];if(r(t))return 1;if(t)return t.length}return 0},t.listenerCount=function(e,t){return e.listenerCount(t)}},74103:(e,t,r)=>{"use strict";var n=r(36571),i=r(19127),a=r(42223),s=r(33371),c=r(67691);function o(e,t,r,i){return new n(e,t,r,i)}o.version=r(16938),o.AlgoliaSearchHelper=n,o.SearchParameters=s,o.RecommendParameters=i,o.SearchResults=c,o.RecommendResults=a,e.exports=o},46732:(e,t,r)=>{"use strict";var n=r(72733);function i(e,t,r){this.main=e,this.fn=t,this.recommendFn=r,this.lastResults=null,this.lastRecommendResults=null}r(73014)(i,n),i.prototype.detach=function(){this.removeAllListeners(),this.main.detachDerivedHelper(this)},i.prototype.getModifiedState=function(e){return this.fn(e)},i.prototype.getModifiedRecommendState=function(e){return this.recommendFn(e)},e.exports=i},19127:e=>{"use strict";function t(e){e=e||{},this.params=e.params||[]}t.prototype={constructor:t,addParams:function(e){var r=this.params.slice();return r.push(e),new t({params:r})},removeParams:function(e){return new t({params:this.params.filter((function(t){return t.$$id!==e}))})},addFrequentlyBoughtTogether:function(e){return this.addParams(Object.assign({},e,{model:"bought-together"}))},addRelatedProducts:function(e){return this.addParams(Object.assign({},e,{model:"related-products"}))},addTrendingItems:function(e){return this.addParams(Object.assign({},e,{model:"trending-items"}))},addTrendingFacets:function(e){return this.addParams(Object.assign({},e,{model:"trending-facets"}))},addLookingSimilar:function(e){return this.addParams(Object.assign({},e,{model:"looking-similar"}))},_buildQueries:function(e,t){return this.params.filter((function(e){return void 0===t[e.$$id]})).map((function(t){var r=Object.assign({},t,{indexName:e,threshold:t.threshold||0});return delete r.$$id,r}))}},e.exports=t},42223:e=>{"use strict";function t(e,t){this._state=e,this._rawResults={};var r=this;e.params.forEach((function(e){var n=e.$$id;r[n]=t[n],r._rawResults[n]=t[n]}))}t.prototype={constructor:t},e.exports=t},44054:(e,t,r)=>{"use strict";var n=r(29110),i=r(40317),a=r(21383),s={addRefinement:function(e,t,r){if(s.isRefined(e,t,r))return e;var i=""+r,a=e[t]?e[t].concat(i):[i],c={};return c[t]=a,n({},c,e)},removeRefinement:function(e,t,r){if(void 0===r)return s.clearRefinement(e,(function(e,r){return t===r}));var n=""+r;return s.clearRefinement(e,(function(e,r){return t===r&&n===e}))},toggleRefinement:function(e,t,r){if(void 0===r)throw new Error("toggleRefinement should be used with a value");return s.isRefined(e,t,r)?s.removeRefinement(e,t,r):s.addRefinement(e,t,r)},clearRefinement:function(e,t,r){if(void 0===t)return i(e)?{}:e;if("string"==typeof t)return a(e,[t]);if("function"==typeof t){var n=!1,s=Object.keys(e).reduce((function(i,a){var s=e[a]||[],c=s.filter((function(e){return!t(e,a,r)}));return c.length!==s.length&&(n=!0),i[a]=c,i}),{});return n?s:e}},isRefined:function(e,t,r){var n=Boolean(e[t])&&e[t].length>0;if(void 0===r||!n)return n;var i=""+r;return-1!==e[t].indexOf(i)}};e.exports=s},33371:(e,t,r)=>{"use strict";var n=r(29110),i=r(20849),a=r(14843),s=r(44728),c=r(40317),o=r(21383),u=r(17507),h=r(72208),l=r(44054);function f(e,t){return Array.isArray(e)&&Array.isArray(t)?e.length===t.length&&e.every((function(e,r){return f(t[r],e)})):e===t}function m(e){var t=e?m._parseNumbers(e):{};void 0===t.userToken||h(t.userToken)||console.warn("[algoliasearch-helper] The `userToken` parameter is invalid. This can lead to wrong analytics.\n - Format: [a-zA-Z0-9_-]{1,64}"),this.facets=t.facets||[],this.disjunctiveFacets=t.disjunctiveFacets||[],this.hierarchicalFacets=t.hierarchicalFacets||[],this.facetsRefinements=t.facetsRefinements||{},this.facetsExcludes=t.facetsExcludes||{},this.disjunctiveFacetsRefinements=t.disjunctiveFacetsRefinements||{},this.numericRefinements=t.numericRefinements||{},this.tagRefinements=t.tagRefinements||[],this.hierarchicalFacetsRefinements=t.hierarchicalFacetsRefinements||{};var r=this;Object.keys(t).forEach((function(e){var n=-1!==m.PARAMETERS.indexOf(e),i=void 0!==t[e];!n&&i&&(r[e]=t[e])}))}m.PARAMETERS=Object.keys(new m),m._parseNumbers=function(e){if(e instanceof m)return e;var t={};if(["aroundPrecision","aroundRadius","getRankingInfo","minWordSizefor2Typos","minWordSizefor1Typo","page","maxValuesPerFacet","distinct","minimumAroundRadius","hitsPerPage","minProximity"].forEach((function(r){var n=e[r];if("string"==typeof n){var i=parseFloat(n);t[r]=isNaN(i)?n:i}})),Array.isArray(e.insideBoundingBox)&&(t.insideBoundingBox=e.insideBoundingBox.map((function(e){return Array.isArray(e)?e.map((function(e){return parseFloat(e)})):e}))),e.numericRefinements){var r={};Object.keys(e.numericRefinements).forEach((function(t){var n=e.numericRefinements[t]||{};r[t]={},Object.keys(n).forEach((function(e){var i=n[e].map((function(e){return Array.isArray(e)?e.map((function(e){return"string"==typeof e?parseFloat(e):e})):"string"==typeof e?parseFloat(e):e}));r[t][e]=i}))})),t.numericRefinements=r}return s(e,t)},m.make=function(e){var t=new m(e);return(e.hierarchicalFacets||[]).forEach((function(e){if(e.rootPath){var r=t.getHierarchicalRefinement(e.name);r.length>0&&0!==r[0].indexOf(e.rootPath)&&(t=t.clearRefinements(e.name)),0===(r=t.getHierarchicalRefinement(e.name)).length&&(t=t.toggleHierarchicalFacetRefinement(e.name,e.rootPath))}})),t},m.validate=function(e,t){var r=t||{};return e.tagFilters&&r.tagRefinements&&r.tagRefinements.length>0?new Error("[Tags] Cannot switch from the managed tag API to the advanced API. It is probably an error, if it is really what you want, you should first clear the tags with clearTags method."):e.tagRefinements.length>0&&r.tagFilters?new Error("[Tags] Cannot switch from the advanced tag API to the managed API. It is probably an error, if it is not, you should first clear the tags with clearTags method."):e.numericFilters&&r.numericRefinements&&c(r.numericRefinements)?new Error("[Numeric filters] Can't switch from the advanced to the managed API. It is probably an error, if this is really what you want, you have to first clear the numeric filters."):c(e.numericRefinements)&&r.numericFilters?new Error("[Numeric filters] Can't switch from the managed API to the advanced. It is probably an error, if this is really what you want, you have to first clear the numeric filters."):null},m.prototype={constructor:m,clearRefinements:function(e){var t={numericRefinements:this._clearNumericRefinements(e),facetsRefinements:l.clearRefinement(this.facetsRefinements,e,"conjunctiveFacet"),facetsExcludes:l.clearRefinement(this.facetsExcludes,e,"exclude"),disjunctiveFacetsRefinements:l.clearRefinement(this.disjunctiveFacetsRefinements,e,"disjunctiveFacet"),hierarchicalFacetsRefinements:l.clearRefinement(this.hierarchicalFacetsRefinements,e,"hierarchicalFacet")};return t.numericRefinements===this.numericRefinements&&t.facetsRefinements===this.facetsRefinements&&t.facetsExcludes===this.facetsExcludes&&t.disjunctiveFacetsRefinements===this.disjunctiveFacetsRefinements&&t.hierarchicalFacetsRefinements===this.hierarchicalFacetsRefinements?this:this.setQueryParameters(t)},clearTags:function(){return void 0===this.tagFilters&&0===this.tagRefinements.length?this:this.setQueryParameters({tagFilters:void 0,tagRefinements:[]})},setIndex:function(e){return e===this.index?this:this.setQueryParameters({index:e})},setQuery:function(e){return e===this.query?this:this.setQueryParameters({query:e})},setPage:function(e){return e===this.page?this:this.setQueryParameters({page:e})},setFacets:function(e){return this.setQueryParameters({facets:e})},setDisjunctiveFacets:function(e){return this.setQueryParameters({disjunctiveFacets:e})},setHitsPerPage:function(e){return this.hitsPerPage===e?this:this.setQueryParameters({hitsPerPage:e})},setTypoTolerance:function(e){return this.typoTolerance===e?this:this.setQueryParameters({typoTolerance:e})},addNumericRefinement:function(e,t,r){var n=u(r);if(this.isNumericRefined(e,t,n))return this;var i=s({},this.numericRefinements);return i[e]=s({},i[e]),i[e][t]?(i[e][t]=i[e][t].slice(),i[e][t].push(n)):i[e][t]=[n],this.setQueryParameters({numericRefinements:i})},getConjunctiveRefinements:function(e){return this.isConjunctiveFacet(e)&&this.facetsRefinements[e]||[]},getDisjunctiveRefinements:function(e){return this.isDisjunctiveFacet(e)&&this.disjunctiveFacetsRefinements[e]||[]},getHierarchicalRefinement:function(e){return this.hierarchicalFacetsRefinements[e]||[]},getExcludeRefinements:function(e){return this.isConjunctiveFacet(e)&&this.facetsExcludes[e]||[]},removeNumericRefinement:function(e,t,r){var n=r;return void 0!==n?this.isNumericRefined(e,t,n)?this.setQueryParameters({numericRefinements:this._clearNumericRefinements((function(r,i){return i===e&&r.op===t&&f(r.val,u(n))}))}):this:void 0!==t?this.isNumericRefined(e,t)?this.setQueryParameters({numericRefinements:this._clearNumericRefinements((function(r,n){return n===e&&r.op===t}))}):this:this.isNumericRefined(e)?this.setQueryParameters({numericRefinements:this._clearNumericRefinements((function(t,r){return r===e}))}):this},getNumericRefinements:function(e){return this.numericRefinements[e]||{}},getNumericRefinement:function(e,t){return this.numericRefinements[e]&&this.numericRefinements[e][t]},_clearNumericRefinements:function(e){if(void 0===e)return c(this.numericRefinements)?{}:this.numericRefinements;if("string"==typeof e)return o(this.numericRefinements,[e]);if("function"==typeof e){var t=!1,r=this.numericRefinements,n=Object.keys(r).reduce((function(n,i){var a=r[i],s={};return a=a||{},Object.keys(a).forEach((function(r){var n=a[r]||[],c=[];n.forEach((function(t){e({val:t,op:r},i,"numeric")||c.push(t)})),c.length!==n.length&&(t=!0),s[r]=c})),n[i]=s,n}),{});return t?n:this.numericRefinements}},addFacet:function(e){return this.isConjunctiveFacet(e)?this:this.setQueryParameters({facets:this.facets.concat([e])})},addDisjunctiveFacet:function(e){return this.isDisjunctiveFacet(e)?this:this.setQueryParameters({disjunctiveFacets:this.disjunctiveFacets.concat([e])})},addHierarchicalFacet:function(e){if(this.isHierarchicalFacet(e.name))throw new Error("Cannot declare two hierarchical facets with the same name: `"+e.name+"`");return this.setQueryParameters({hierarchicalFacets:this.hierarchicalFacets.concat([e])})},addFacetRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return l.isRefined(this.facetsRefinements,e,t)?this:this.setQueryParameters({facetsRefinements:l.addRefinement(this.facetsRefinements,e,t)})},addExcludeRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return l.isRefined(this.facetsExcludes,e,t)?this:this.setQueryParameters({facetsExcludes:l.addRefinement(this.facetsExcludes,e,t)})},addDisjunctiveFacetRefinement:function(e,t){if(!this.isDisjunctiveFacet(e))throw new Error(e+" is not defined in the disjunctiveFacets attribute of the helper configuration");return l.isRefined(this.disjunctiveFacetsRefinements,e,t)?this:this.setQueryParameters({disjunctiveFacetsRefinements:l.addRefinement(this.disjunctiveFacetsRefinements,e,t)})},addTagRefinement:function(e){if(this.isTagRefined(e))return this;var t={tagRefinements:this.tagRefinements.concat(e)};return this.setQueryParameters(t)},removeFacet:function(e){return this.isConjunctiveFacet(e)?this.clearRefinements(e).setQueryParameters({facets:this.facets.filter((function(t){return t!==e}))}):this},removeDisjunctiveFacet:function(e){return this.isDisjunctiveFacet(e)?this.clearRefinements(e).setQueryParameters({disjunctiveFacets:this.disjunctiveFacets.filter((function(t){return t!==e}))}):this},removeHierarchicalFacet:function(e){return this.isHierarchicalFacet(e)?this.clearRefinements(e).setQueryParameters({hierarchicalFacets:this.hierarchicalFacets.filter((function(t){return t.name!==e}))}):this},removeFacetRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return l.isRefined(this.facetsRefinements,e,t)?this.setQueryParameters({facetsRefinements:l.removeRefinement(this.facetsRefinements,e,t)}):this},removeExcludeRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return l.isRefined(this.facetsExcludes,e,t)?this.setQueryParameters({facetsExcludes:l.removeRefinement(this.facetsExcludes,e,t)}):this},removeDisjunctiveFacetRefinement:function(e,t){if(!this.isDisjunctiveFacet(e))throw new Error(e+" is not defined in the disjunctiveFacets attribute of the helper configuration");return l.isRefined(this.disjunctiveFacetsRefinements,e,t)?this.setQueryParameters({disjunctiveFacetsRefinements:l.removeRefinement(this.disjunctiveFacetsRefinements,e,t)}):this},removeTagRefinement:function(e){if(!this.isTagRefined(e))return this;var t={tagRefinements:this.tagRefinements.filter((function(t){return t!==e}))};return this.setQueryParameters(t)},toggleRefinement:function(e,t){return this.toggleFacetRefinement(e,t)},toggleFacetRefinement:function(e,t){if(this.isHierarchicalFacet(e))return this.toggleHierarchicalFacetRefinement(e,t);if(this.isConjunctiveFacet(e))return this.toggleConjunctiveFacetRefinement(e,t);if(this.isDisjunctiveFacet(e))return this.toggleDisjunctiveFacetRefinement(e,t);throw new Error("Cannot refine the undeclared facet "+e+"; it should be added to the helper options facets, disjunctiveFacets or hierarchicalFacets")},toggleConjunctiveFacetRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return this.setQueryParameters({facetsRefinements:l.toggleRefinement(this.facetsRefinements,e,t)})},toggleExcludeFacetRefinement:function(e,t){if(!this.isConjunctiveFacet(e))throw new Error(e+" is not defined in the facets attribute of the helper configuration");return this.setQueryParameters({facetsExcludes:l.toggleRefinement(this.facetsExcludes,e,t)})},toggleDisjunctiveFacetRefinement:function(e,t){if(!this.isDisjunctiveFacet(e))throw new Error(e+" is not defined in the disjunctiveFacets attribute of the helper configuration");return this.setQueryParameters({disjunctiveFacetsRefinements:l.toggleRefinement(this.disjunctiveFacetsRefinements,e,t)})},toggleHierarchicalFacetRefinement:function(e,t){if(!this.isHierarchicalFacet(e))throw new Error(e+" is not defined in the hierarchicalFacets attribute of the helper configuration");var r=this._getHierarchicalFacetSeparator(this.getHierarchicalFacetByName(e)),i={};return void 0!==this.hierarchicalFacetsRefinements[e]&&this.hierarchicalFacetsRefinements[e].length>0&&(this.hierarchicalFacetsRefinements[e][0]===t||0===this.hierarchicalFacetsRefinements[e][0].indexOf(t+r))?-1===t.indexOf(r)?i[e]=[]:i[e]=[t.slice(0,t.lastIndexOf(r))]:i[e]=[t],this.setQueryParameters({hierarchicalFacetsRefinements:n({},i,this.hierarchicalFacetsRefinements)})},addHierarchicalFacetRefinement:function(e,t){if(this.isHierarchicalFacetRefined(e))throw new Error(e+" is already refined.");if(!this.isHierarchicalFacet(e))throw new Error(e+" is not defined in the hierarchicalFacets attribute of the helper configuration.");var r={};return r[e]=[t],this.setQueryParameters({hierarchicalFacetsRefinements:n({},r,this.hierarchicalFacetsRefinements)})},removeHierarchicalFacetRefinement:function(e){if(!this.isHierarchicalFacetRefined(e))return this;var t={};return t[e]=[],this.setQueryParameters({hierarchicalFacetsRefinements:n({},t,this.hierarchicalFacetsRefinements)})},toggleTagRefinement:function(e){return this.isTagRefined(e)?this.removeTagRefinement(e):this.addTagRefinement(e)},isDisjunctiveFacet:function(e){return this.disjunctiveFacets.indexOf(e)>-1},isHierarchicalFacet:function(e){return void 0!==this.getHierarchicalFacetByName(e)},isConjunctiveFacet:function(e){return this.facets.indexOf(e)>-1},isFacetRefined:function(e,t){return!!this.isConjunctiveFacet(e)&&l.isRefined(this.facetsRefinements,e,t)},isExcludeRefined:function(e,t){return!!this.isConjunctiveFacet(e)&&l.isRefined(this.facetsExcludes,e,t)},isDisjunctiveFacetRefined:function(e,t){return!!this.isDisjunctiveFacet(e)&&l.isRefined(this.disjunctiveFacetsRefinements,e,t)},isHierarchicalFacetRefined:function(e,t){if(!this.isHierarchicalFacet(e))return!1;var r=this.getHierarchicalRefinement(e);return t?-1!==r.indexOf(t):r.length>0},isNumericRefined:function(e,t,r){if(void 0===r&&void 0===t)return Boolean(this.numericRefinements[e]);var n=this.numericRefinements[e]&&void 0!==this.numericRefinements[e][t];if(void 0===r||!n)return n;var a,s,c=u(r),o=void 0!==(a=this.numericRefinements[e][t],s=c,i(a,(function(e){return f(e,s)})));return n&&o},isTagRefined:function(e){return-1!==this.tagRefinements.indexOf(e)},getRefinedDisjunctiveFacets:function(){var e=this,t=a(Object.keys(this.numericRefinements).filter((function(t){return Object.keys(e.numericRefinements[t]).length>0})),this.disjunctiveFacets);return Object.keys(this.disjunctiveFacetsRefinements).filter((function(t){return e.disjunctiveFacetsRefinements[t].length>0})).concat(t).concat(this.getRefinedHierarchicalFacets()).sort()},getRefinedHierarchicalFacets:function(){var e=this;return a(this.hierarchicalFacets.map((function(e){return e.name})),Object.keys(this.hierarchicalFacetsRefinements).filter((function(t){return e.hierarchicalFacetsRefinements[t].length>0}))).sort()},getUnrefinedDisjunctiveFacets:function(){var e=this.getRefinedDisjunctiveFacets();return this.disjunctiveFacets.filter((function(t){return-1===e.indexOf(t)}))},managedParameters:["index","facets","disjunctiveFacets","facetsRefinements","hierarchicalFacets","facetsExcludes","disjunctiveFacetsRefinements","numericRefinements","tagRefinements","hierarchicalFacetsRefinements"],getQueryParams:function(){var e=this.managedParameters,t={},r=this;return Object.keys(this).forEach((function(n){var i=r[n];-1===e.indexOf(n)&&void 0!==i&&(t[n]=i)})),t},setQueryParameter:function(e,t){if(this[e]===t)return this;var r={};return r[e]=t,this.setQueryParameters(r)},setQueryParameters:function(e){if(!e)return this;var t=m.validate(this,e);if(t)throw t;var r=this,n=m._parseNumbers(e),i=Object.keys(this).reduce((function(e,t){return e[t]=r[t],e}),{}),a=Object.keys(n).reduce((function(e,t){var r=void 0!==e[t],i=void 0!==n[t];return r&&!i?o(e,[t]):(i&&(e[t]=n[t]),e)}),i);return new this.constructor(a)},resetPage:function(){return void 0===this.page?this:this.setPage(0)},_getHierarchicalFacetSortBy:function(e){return e.sortBy||["isRefined:desc","name:asc"]},_getHierarchicalFacetSeparator:function(e){return e.separator||" > "},_getHierarchicalRootPath:function(e){return e.rootPath||null},_getHierarchicalShowParentLevel:function(e){return"boolean"!=typeof e.showParentLevel||e.showParentLevel},getHierarchicalFacetByName:function(e){return i(this.hierarchicalFacets,(function(t){return t.name===e}))},getHierarchicalFacetBreadcrumb:function(e){if(!this.isHierarchicalFacet(e))return[];var t=this.getHierarchicalRefinement(e)[0];if(!t)return[];var r=this._getHierarchicalFacetSeparator(this.getHierarchicalFacetByName(e));return t.split(r).map((function(e){return e.trim()}))},toString:function(){return JSON.stringify(this,null,2)}},e.exports=m},76673:(e,t,r)=>{"use strict";e.exports=function(e){return function(t,r){var n=e.hierarchicalFacets[r],u=e.hierarchicalFacetsRefinements[n.name]&&e.hierarchicalFacetsRefinements[n.name][0]||"",h=e._getHierarchicalFacetSeparator(n),l=e._getHierarchicalRootPath(n),f=e._getHierarchicalShowParentLevel(n),m=a(e._getHierarchicalFacetSortBy(n)),d=t.every((function(e){return e.exhaustive})),p=function(e,t,r,n,a){return function(u,h,l){var f=u;if(l>0){var m=0;for(f=u;m{"use strict";var n=r(78965),i=r(29110),a=r(2909),s=r(20849),c=r(43917),o=r(7577),u=r(44728),h=r(38601),l=a.escapeFacetValue,f=a.unescapeFacetValue,m=r(76673);function d(e){var t={};return e.forEach((function(e,r){t[e]=r})),t}function p(e,t,r){t&&t[r]&&(e.stats=t[r])}function g(e,t,r){var a=t[0]||{};this._rawResults=t;var o=this;Object.keys(a).forEach((function(e){o[e]=a[e]}));var h=u({persistHierarchicalRootCount:!1},r);Object.keys(h).forEach((function(e){o[e]=h[e]})),this.processingTimeMS=t.reduce((function(e,t){return void 0===t.processingTimeMS?e:e+t.processingTimeMS}),0),this.disjunctiveFacets=[],this.hierarchicalFacets=e.hierarchicalFacets.map((function(){return[]})),this.facets=[];var l=e.getRefinedDisjunctiveFacets(),g=d(e.facets),v=d(e.disjunctiveFacets),y=1,R=a.facets||{};Object.keys(R).forEach((function(t){var r,n,i=R[t],u=(r=e.hierarchicalFacets,n=t,s(r,(function(e){return(e.attributes||[]).indexOf(n)>-1})));if(u){var h=u.attributes.indexOf(t),l=c(e.hierarchicalFacets,(function(e){return e.name===u.name}));o.hierarchicalFacets[l][h]={attribute:t,data:i,exhaustive:a.exhaustiveFacetsCount}}else{var f,m=-1!==e.disjunctiveFacets.indexOf(t),d=-1!==e.facets.indexOf(t);m&&(f=v[t],o.disjunctiveFacets[f]={name:t,data:i,exhaustive:a.exhaustiveFacetsCount},p(o.disjunctiveFacets[f],a.facets_stats,t)),d&&(f=g[t],o.facets[f]={name:t,data:i,exhaustive:a.exhaustiveFacetsCount},p(o.facets[f],a.facets_stats,t))}})),this.hierarchicalFacets=n(this.hierarchicalFacets),l.forEach((function(r){var n=t[y],s=n&&n.facets?n.facets:{},h=e.getHierarchicalFacetByName(r);Object.keys(s).forEach((function(t){var r,l=s[t];if(h){r=c(e.hierarchicalFacets,(function(e){return e.name===h.name}));var m=c(o.hierarchicalFacets[r],(function(e){return e.attribute===t}));if(-1===m)return;o.hierarchicalFacets[r][m].data=u({},o.hierarchicalFacets[r][m].data,l)}else{r=v[t];var d=a.facets&&a.facets[t]||{};o.disjunctiveFacets[r]={name:t,data:i({},l,d),exhaustive:n.exhaustiveFacetsCount},p(o.disjunctiveFacets[r],n.facets_stats,t),e.disjunctiveFacetsRefinements[t]&&e.disjunctiveFacetsRefinements[t].forEach((function(n){!o.disjunctiveFacets[r].data[n]&&e.disjunctiveFacetsRefinements[t].indexOf(f(n))>-1&&(o.disjunctiveFacets[r].data[n]=0)}))}})),y++})),e.getRefinedHierarchicalFacets().forEach((function(r){var n=e.getHierarchicalFacetByName(r),a=e._getHierarchicalFacetSeparator(n),s=e.getHierarchicalRefinement(r);0===s.length||s[0].split(a).length<2||t.slice(y).forEach((function(t){var r=t&&t.facets?t.facets:{};Object.keys(r).forEach((function(t){var u=r[t],h=c(e.hierarchicalFacets,(function(e){return e.name===n.name})),l=c(o.hierarchicalFacets[h],(function(e){return e.attribute===t}));if(-1!==l){var f={};if(s.length>0&&!o.persistHierarchicalRootCount){var m=s[0].split(a)[0];f[m]=o.hierarchicalFacets[h][l].data[m]}o.hierarchicalFacets[h][l].data=i(f,u,o.hierarchicalFacets[h][l].data)}})),y++}))})),Object.keys(e.facetsExcludes).forEach((function(t){var r=e.facetsExcludes[t],n=g[t];o.facets[n]={name:t,data:R[t],exhaustive:a.exhaustiveFacetsCount},r.forEach((function(e){o.facets[n]=o.facets[n]||{name:t},o.facets[n].data=o.facets[n].data||{},o.facets[n].data[e]=0}))})),this.hierarchicalFacets=this.hierarchicalFacets.map(m(e)),this.facets=n(this.facets),this.disjunctiveFacets=n(this.disjunctiveFacets),this._state=e}function v(e,t){function r(e){return e.name===t}if(e._state.isConjunctiveFacet(t)){var n=s(e.facets,r);return n?Object.keys(n.data).map((function(r){var i=l(r);return{name:r,escapedValue:i,count:n.data[r],isRefined:e._state.isFacetRefined(t,i),isExcluded:e._state.isExcludeRefined(t,r)}})):[]}if(e._state.isDisjunctiveFacet(t)){var i=s(e.disjunctiveFacets,r);return i?Object.keys(i.data).map((function(r){var n=l(r);return{name:r,escapedValue:n,count:i.data[r],isRefined:e._state.isDisjunctiveFacetRefined(t,n)}})):[]}if(e._state.isHierarchicalFacet(t)){var a=s(e.hierarchicalFacets,r);if(!a)return a;var c=e._state.getHierarchicalFacetByName(t),o=e._state._getHierarchicalFacetSeparator(c),u=f(e._state.getHierarchicalRefinement(t)[0]||"");0===u.indexOf(c.rootPath)&&(u=u.replace(c.rootPath+o,""));var h=u.split(o);return h.unshift(t),y(a,h,0),a}}function y(e,t,r){e.isRefined=e.name===(t[r]&&t[r].trim()),e.data&&e.data.forEach((function(e){y(e,t,r+1)}))}function R(e,t,r,n){if(n=n||0,Array.isArray(t))return e(t,r[n]);if(!t.data||0===t.data.length)return t;var a=t.data.map((function(t){return R(e,t,r,n+1)})),s=e(a,r[n]);return i({data:s},t)}function F(e,t){var r=s(e,(function(e){return e.name===t}));return r&&r.stats}function b(e,t,r,n,i){var a=s(i,(function(e){return e.name===r})),c=a&&a.data&&a.data[n]?a.data[n]:0,o=a&&a.exhaustive||!1;return{type:t,attributeName:r,name:n,count:c,exhaustive:o}}g.prototype.getFacetByName=function(e){function t(t){return t.name===e}return s(this.facets,t)||s(this.disjunctiveFacets,t)||s(this.hierarchicalFacets,t)},g.DEFAULT_SORT=["isRefined:desc","count:desc","name:asc"],g.prototype.getFacetValues=function(e,t){var r=v(this,e);if(r){var n,a=i({},t,{sortBy:g.DEFAULT_SORT,facetOrdering:!(t&&t.sortBy)}),s=this;if(Array.isArray(r))n=[e];else n=s._state.getHierarchicalFacetByName(r.name).attributes;return R((function(e,t){if(a.facetOrdering){var r=function(e,t){return e.renderingContent&&e.renderingContent.facetOrdering&&e.renderingContent.facetOrdering.values&&e.renderingContent.facetOrdering.values[t]}(s,t);if(r)return function(e,t){var r=[],n=[],i=t.hide||[],a=(t.order||[]).reduce((function(e,t,r){return e[t]=r,e}),{});e.forEach((function(e){var t=e.path||e.name,s=i.indexOf(t)>-1;s||void 0===a[t]?s||n.push(e):r[a[t]]=e})),r=r.filter((function(e){return e}));var s,c=t.sortRemainingBy;return"hidden"===c?r:(s="alpha"===c?[["path","name"],["asc","asc"]]:[["count"],["desc"]],r.concat(h(n,s[0],s[1])))}(e,r)}if(Array.isArray(a.sortBy)){var n=o(a.sortBy,g.DEFAULT_SORT);return h(e,n[0],n[1])}if("function"==typeof a.sortBy)return function(e,t){return t.sort(e)}(a.sortBy,e);throw new Error("options.sortBy is optional but if defined it must be either an array of string (predicates) or a sorting function")}),r,n)}},g.prototype.getFacetStats=function(e){return this._state.isConjunctiveFacet(e)?F(this.facets,e):this._state.isDisjunctiveFacet(e)?F(this.disjunctiveFacets,e):void 0},g.prototype.getRefinements=function(){var e=this._state,t=this,r=[];return Object.keys(e.facetsRefinements).forEach((function(n){e.facetsRefinements[n].forEach((function(i){r.push(b(e,"facet",n,i,t.facets))}))})),Object.keys(e.facetsExcludes).forEach((function(n){e.facetsExcludes[n].forEach((function(i){r.push(b(e,"exclude",n,i,t.facets))}))})),Object.keys(e.disjunctiveFacetsRefinements).forEach((function(n){e.disjunctiveFacetsRefinements[n].forEach((function(i){r.push(b(e,"disjunctive",n,i,t.disjunctiveFacets))}))})),Object.keys(e.hierarchicalFacetsRefinements).forEach((function(n){e.hierarchicalFacetsRefinements[n].forEach((function(i){r.push(function(e,t,r,n){var i=e.getHierarchicalFacetByName(t),a=e._getHierarchicalFacetSeparator(i),c=r.split(a),o=s(n,(function(e){return e.name===t})),u=c.reduce((function(e,t){var r=e&&s(e.data,(function(e){return e.name===t}));return void 0!==r?r:e}),o),h=u&&u.count||0,l=u&&u.exhaustive||!1,f=u&&u.path||"";return{type:"hierarchical",attributeName:t,name:f,count:h,exhaustive:l}}(e,n,i,t.hierarchicalFacets))}))})),Object.keys(e.numericRefinements).forEach((function(t){var n=e.numericRefinements[t];Object.keys(n).forEach((function(e){n[e].forEach((function(n){r.push({type:"numeric",attributeName:t,name:n,numericValue:n,operator:e})}))}))})),e.tagRefinements.forEach((function(e){r.push({type:"tag",attributeName:"_tags",name:e})})),r},e.exports=g},36571:(e,t,r)=>{"use strict";var n=r(72733),i=r(46732),a=r(2909).escapeFacetValue,s=r(73014),c=r(44728),o=r(40317),u=r(21383),h=r(19127),l=r(42223),f=r(49228),m=r(33371),d=r(67691),p=r(57749),g=r(16938);function v(e,t,r,n){"function"==typeof e.addAlgoliaAgent&&e.addAlgoliaAgent("JS Helper ("+g+")"),this.setClient(e);var i=r||{};i.index=t,this.state=m.make(i),this.recommendState=new h({params:i.recommendState}),this.lastResults=null,this.lastRecommendResults=null,this._queryId=0,this._recommendQueryId=0,this._lastQueryIdReceived=-1,this._lastRecommendQueryIdReceived=-1,this.derivedHelpers=[],this._currentNbQueries=0,this._currentNbRecommendQueries=0,this._searchResultsOptions=n,this._recommendCache={}}function y(e){if(e<0)throw new Error("Page requested below 0.");return this._change({state:this.state.setPage(e),isPageReset:!1}),this}function R(){return this.state.page}s(v,n),v.prototype.search=function(){return this._search({onlyWithDerivedHelpers:!1}),this},v.prototype.searchOnlyWithDerivedHelpers=function(){return this._search({onlyWithDerivedHelpers:!0}),this},v.prototype.recommend=function(){return this._recommend(),this},v.prototype.getQuery=function(){var e=this.state;return f._getHitsSearchParams(e)},v.prototype.searchOnce=function(e,t){var r=e?this.state.setQueryParameters(e):this.state,n=f._getQueries(r.index,r),i=this;if(this._currentNbQueries++,this.emit("searchOnce",{state:r}),!t)return this.client.search(n).then((function(e){return i._currentNbQueries--,0===i._currentNbQueries&&i.emit("searchQueueEmpty"),{content:new d(r,e.results),state:r,_originalResponse:e}}),(function(e){throw i._currentNbQueries--,0===i._currentNbQueries&&i.emit("searchQueueEmpty"),e}));this.client.search(n).then((function(e){i._currentNbQueries--,0===i._currentNbQueries&&i.emit("searchQueueEmpty"),t(null,new d(r,e.results),r)})).catch((function(e){i._currentNbQueries--,0===i._currentNbQueries&&i.emit("searchQueueEmpty"),t(e,null,r)}))},v.prototype.findAnswers=function(e){console.warn("[algoliasearch-helper] answers is no longer supported");var t=this.state,r=this.derivedHelpers[0];if(!r)return Promise.resolve([]);var n=r.getModifiedState(t),i=c({attributesForPrediction:e.attributesForPrediction,nbHits:e.nbHits},{params:u(f._getHitsSearchParams(n),["attributesToSnippet","hitsPerPage","restrictSearchableAttributes","snippetEllipsisText"])}),a="search for answers was called, but this client does not have a function client.initIndex(index).findAnswers";if("function"!=typeof this.client.initIndex)throw new Error(a);var s=this.client.initIndex(n.index);if("function"!=typeof s.findAnswers)throw new Error(a);return s.findAnswers(n.query,e.queryLanguages,i)},v.prototype.searchForFacetValues=function(e,t,r,n){var i="function"==typeof this.client.searchForFacetValues,s="function"==typeof this.client.initIndex;if(!i&&!s&&"function"!=typeof this.client.search)throw new Error("search for facet values (searchable) was called, but this client does not have a function client.searchForFacetValues or client.initIndex(index).searchForFacetValues");var c=this.state.setQueryParameters(n||{}),o=c.isDisjunctiveFacet(e),u=f.getSearchForFacetQuery(e,t,r,c);this._currentNbQueries++;var h,l=this;return i?h=this.client.searchForFacetValues([{indexName:c.index,params:u}]):s?h=this.client.initIndex(c.index).searchForFacetValues(u):(delete u.facetName,h=this.client.search([{type:"facet",facet:e,indexName:c.index,params:u}]).then((function(e){return e.results[0]}))),this.emit("searchForFacetValues",{state:c,facet:e,query:t}),h.then((function(t){return l._currentNbQueries--,0===l._currentNbQueries&&l.emit("searchQueueEmpty"),(t=Array.isArray(t)?t[0]:t).facetHits.forEach((function(t){t.escapedValue=a(t.value),t.isRefined=o?c.isDisjunctiveFacetRefined(e,t.escapedValue):c.isFacetRefined(e,t.escapedValue)})),t}),(function(e){throw l._currentNbQueries--,0===l._currentNbQueries&&l.emit("searchQueueEmpty"),e}))},v.prototype.setQuery=function(e){return this._change({state:this.state.resetPage().setQuery(e),isPageReset:!0}),this},v.prototype.clearRefinements=function(e){return this._change({state:this.state.resetPage().clearRefinements(e),isPageReset:!0}),this},v.prototype.clearTags=function(){return this._change({state:this.state.resetPage().clearTags(),isPageReset:!0}),this},v.prototype.addDisjunctiveFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().addDisjunctiveFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.addDisjunctiveRefine=function(){return this.addDisjunctiveFacetRefinement.apply(this,arguments)},v.prototype.addHierarchicalFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().addHierarchicalFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.addNumericRefinement=function(e,t,r){return this._change({state:this.state.resetPage().addNumericRefinement(e,t,r),isPageReset:!0}),this},v.prototype.addFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().addFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.addRefine=function(){return this.addFacetRefinement.apply(this,arguments)},v.prototype.addFacetExclusion=function(e,t){return this._change({state:this.state.resetPage().addExcludeRefinement(e,t),isPageReset:!0}),this},v.prototype.addExclude=function(){return this.addFacetExclusion.apply(this,arguments)},v.prototype.addTag=function(e){return this._change({state:this.state.resetPage().addTagRefinement(e),isPageReset:!0}),this},v.prototype.addFrequentlyBoughtTogether=function(e){return this._recommendChange({state:this.recommendState.addFrequentlyBoughtTogether(e)}),this},v.prototype.addRelatedProducts=function(e){return this._recommendChange({state:this.recommendState.addRelatedProducts(e)}),this},v.prototype.addTrendingItems=function(e){return this._recommendChange({state:this.recommendState.addTrendingItems(e)}),this},v.prototype.addTrendingFacets=function(e){return this._recommendChange({state:this.recommendState.addTrendingFacets(e)}),this},v.prototype.addLookingSimilar=function(e){return this._recommendChange({state:this.recommendState.addLookingSimilar(e)}),this},v.prototype.removeNumericRefinement=function(e,t,r){return this._change({state:this.state.resetPage().removeNumericRefinement(e,t,r),isPageReset:!0}),this},v.prototype.removeDisjunctiveFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().removeDisjunctiveFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.removeDisjunctiveRefine=function(){return this.removeDisjunctiveFacetRefinement.apply(this,arguments)},v.prototype.removeHierarchicalFacetRefinement=function(e){return this._change({state:this.state.resetPage().removeHierarchicalFacetRefinement(e),isPageReset:!0}),this},v.prototype.removeFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().removeFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.removeRefine=function(){return this.removeFacetRefinement.apply(this,arguments)},v.prototype.removeFacetExclusion=function(e,t){return this._change({state:this.state.resetPage().removeExcludeRefinement(e,t),isPageReset:!0}),this},v.prototype.removeExclude=function(){return this.removeFacetExclusion.apply(this,arguments)},v.prototype.removeTag=function(e){return this._change({state:this.state.resetPage().removeTagRefinement(e),isPageReset:!0}),this},v.prototype.removeFrequentlyBoughtTogether=function(e){return this._recommendChange({state:this.recommendState.removeParams(e)}),this},v.prototype.removeRelatedProducts=function(e){return this._recommendChange({state:this.recommendState.removeParams(e)}),this},v.prototype.removeTrendingItems=function(e){return this._recommendChange({state:this.recommendState.removeParams(e)}),this},v.prototype.removeTrendingFacets=function(e){return this._recommendChange({state:this.recommendState.removeParams(e)}),this},v.prototype.removeLookingSimilar=function(e){return this._recommendChange({state:this.recommendState.removeParams(e)}),this},v.prototype.toggleFacetExclusion=function(e,t){return this._change({state:this.state.resetPage().toggleExcludeFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.toggleExclude=function(){return this.toggleFacetExclusion.apply(this,arguments)},v.prototype.toggleRefinement=function(e,t){return this.toggleFacetRefinement(e,t)},v.prototype.toggleFacetRefinement=function(e,t){return this._change({state:this.state.resetPage().toggleFacetRefinement(e,t),isPageReset:!0}),this},v.prototype.toggleRefine=function(){return this.toggleFacetRefinement.apply(this,arguments)},v.prototype.toggleTag=function(e){return this._change({state:this.state.resetPage().toggleTagRefinement(e),isPageReset:!0}),this},v.prototype.nextPage=function(){var e=this.state.page||0;return this.setPage(e+1)},v.prototype.previousPage=function(){var e=this.state.page||0;return this.setPage(e-1)},v.prototype.setCurrentPage=y,v.prototype.setPage=y,v.prototype.setIndex=function(e){return this._change({state:this.state.resetPage().setIndex(e),isPageReset:!0}),this},v.prototype.setQueryParameter=function(e,t){return this._change({state:this.state.resetPage().setQueryParameter(e,t),isPageReset:!0}),this},v.prototype.setState=function(e){return this._change({state:m.make(e),isPageReset:!1}),this},v.prototype.overrideStateWithoutTriggeringChangeEvent=function(e){return this.state=new m(e),this},v.prototype.hasRefinements=function(e){return!!o(this.state.getNumericRefinements(e))||(this.state.isConjunctiveFacet(e)?this.state.isFacetRefined(e):this.state.isDisjunctiveFacet(e)?this.state.isDisjunctiveFacetRefined(e):!!this.state.isHierarchicalFacet(e)&&this.state.isHierarchicalFacetRefined(e))},v.prototype.isExcluded=function(e,t){return this.state.isExcludeRefined(e,t)},v.prototype.isDisjunctiveRefined=function(e,t){return this.state.isDisjunctiveFacetRefined(e,t)},v.prototype.hasTag=function(e){return this.state.isTagRefined(e)},v.prototype.isTagRefined=function(){return this.hasTagRefinements.apply(this,arguments)},v.prototype.getIndex=function(){return this.state.index},v.prototype.getCurrentPage=R,v.prototype.getPage=R,v.prototype.getTags=function(){return this.state.tagRefinements},v.prototype.getRefinements=function(e){var t=[];if(this.state.isConjunctiveFacet(e))this.state.getConjunctiveRefinements(e).forEach((function(e){t.push({value:e,type:"conjunctive"})})),this.state.getExcludeRefinements(e).forEach((function(e){t.push({value:e,type:"exclude"})}));else if(this.state.isDisjunctiveFacet(e)){this.state.getDisjunctiveRefinements(e).forEach((function(e){t.push({value:e,type:"disjunctive"})}))}var r=this.state.getNumericRefinements(e);return Object.keys(r).forEach((function(e){var n=r[e];t.push({value:n,operator:e,type:"numeric"})})),t},v.prototype.getNumericRefinement=function(e,t){return this.state.getNumericRefinement(e,t)},v.prototype.getHierarchicalFacetBreadcrumb=function(e){return this.state.getHierarchicalFacetBreadcrumb(e)},v.prototype._search=function(e){var t=this.state,r=[],n=[];e.onlyWithDerivedHelpers||(n=f._getQueries(t.index,t),r.push({state:t,queriesCount:n.length,helper:this}),this.emit("search",{state:t,results:this.lastResults}));var i=this.derivedHelpers.map((function(e){var n=e.getModifiedState(t),i=n.index?f._getQueries(n.index,n):[];return r.push({state:n,queriesCount:i.length,helper:e}),e.emit("search",{state:n,results:e.lastResults}),i})),a=Array.prototype.concat.apply(n,i),s=this._queryId++;if(this._currentNbQueries++,!a.length)return Promise.resolve({results:[]}).then(this._dispatchAlgoliaResponse.bind(this,r,s));try{this.client.search(a).then(this._dispatchAlgoliaResponse.bind(this,r,s)).catch(this._dispatchAlgoliaError.bind(this,s))}catch(c){this.emit("error",{error:c})}},v.prototype._recommend=function(){var e=this.state,t=this.recommendState,r=this.getIndex(),n=[{state:t,index:r,helper:this}],i=t.params.map((function(e){return e.$$id}));this.emit("fetch",{recommend:{state:t,results:this.lastRecommendResults}});var a=this._recommendCache,s=this.derivedHelpers.map((function(t){var r=t.getModifiedState(e).index;if(!r)return[];var s=t.getModifiedRecommendState(new h);return n.push({state:s,index:r,helper:t}),i=Array.prototype.concat.apply(i,s.params.map((function(e){return e.$$id}))),t.emit("fetch",{recommend:{state:s,results:t.lastRecommendResults}}),s._buildQueries(r,a)})),c=Array.prototype.concat.apply(this.recommendState._buildQueries(r,a),s);if(0!==c.length)if(c.length>0&&void 0===this.client.getRecommendations)console.warn("Please update algoliasearch/lite to the latest version in order to use recommend widgets.");else{var o=this._recommendQueryId++;this._currentNbRecommendQueries++;try{this.client.getRecommendations(c).then(this._dispatchRecommendResponse.bind(this,o,n,i)).catch(this._dispatchRecommendError.bind(this,o))}catch(u){this.emit("error",{error:u})}}},v.prototype._dispatchAlgoliaResponse=function(e,t,r){var n=this;if(!(t0},v.prototype._change=function(e){var t=e.state,r=e.isPageReset;t!==this.state&&(this.state=t,this.emit("change",{state:this.state,results:this.lastResults,isPageReset:r}))},v.prototype._recommendChange=function(e){var t=e.state;t!==this.recommendState&&(this.recommendState=t,this.emit("recommend:change",{search:{results:this.lastResults,state:this.state},recommend:{results:this.lastRecommendResults,state:this.recommendState}}))},v.prototype.clearCache=function(){return this.client.clearCache&&this.client.clearCache(),this},v.prototype.setClient=function(e){return this.client===e||("function"==typeof e.addAlgoliaAgent&&e.addAlgoliaAgent("JS Helper ("+g+")"),this.client=e),this},v.prototype.getClient=function(){return this.client},v.prototype.derive=function(e,t){var r=new i(this,e,t);return this.derivedHelpers.push(r),r},v.prototype.detachDerivedHelper=function(e){var t=this.derivedHelpers.indexOf(e);if(-1===t)throw new Error("Derived helper already detached");this.derivedHelpers.splice(t,1)},v.prototype.hasPendingRequests=function(){return this._currentNbQueries>0},e.exports=v},78965:e=>{"use strict";e.exports=function(e){return Array.isArray(e)?e.filter(Boolean):[]}},29110:e=>{"use strict";e.exports=function(){return Array.prototype.slice.call(arguments).reduceRight((function(e,t){return Object.keys(Object(t)).forEach((function(r){void 0!==t[r]&&(void 0!==e[r]&&delete e[r],e[r]=t[r])})),e}),{})}},2909:e=>{"use strict";e.exports={escapeFacetValue:function(e){return"string"!=typeof e?e:String(e).replace(/^-/,"\\-")},unescapeFacetValue:function(e){return"string"!=typeof e?e:e.replace(/^\\-/,"-")}}},20849:e=>{"use strict";e.exports=function(e,t){if(Array.isArray(e))for(var r=0;r{"use strict";e.exports=function(e,t){if(!Array.isArray(e))return-1;for(var r=0;r{e.exports=function(e){return e.reduce((function(e,t){return e.concat(t)}),[])}},7577:(e,t,r)=>{"use strict";var n=r(20849);e.exports=function(e,t){var r=(t||[]).map((function(e){return e.split(":")}));return e.reduce((function(e,t){var i=t.split(":"),a=n(r,(function(e){return e[0]===i[0]}));return i.length>1||!a?(e[0].push(i[0]),e[1].push(i[1]),e):(e[0].push(a[0]),e[1].push(a[1]),e)}),[[],[]])}},73014:e=>{"use strict";e.exports=function(e,t){e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}},14843:e=>{"use strict";e.exports=function(e,t){return e.filter((function(r,n){return t.indexOf(r)>-1&&e.indexOf(r)===n}))}},44728:e=>{"use strict";function t(e){return"function"==typeof e||Array.isArray(e)||"[object Object]"===Object.prototype.toString.call(e)}function r(e,n){if(e===n)return e;for(var i in n)if(Object.prototype.hasOwnProperty.call(n,i)&&"__proto__"!==i&&"constructor"!==i){var a=n[i],s=e[i];void 0!==s&&void 0===a||(t(s)&&t(a)?e[i]=r(s,a):e[i]="object"==typeof(c=a)&&null!==c?r(Array.isArray(c)?[]:{},c):c)}var c;return e}e.exports=function(e){t(e)||(e={});for(var n=1,i=arguments.length;n{"use strict";e.exports=function(e){return e&&Object.keys(e).length>0}},21383:e=>{"use strict";e.exports=function(e,t){if(null===e)return{};var r,n,i={},a=Object.keys(e);for(n=0;n=0||(i[r]=e[r]);return i}},38601:e=>{"use strict";function t(e,t){if(e!==t){var r=void 0!==e,n=null===e,i=void 0!==t,a=null===t;if(!a&&e>t||n&&i||!r)return 1;if(!n&&e=n.length?a:"desc"===n[i]?-a:a}return e.index-r.index})),i.map((function(e){return e.value}))}},17507:e=>{"use strict";e.exports=function e(t){if("number"==typeof t)return t;if("string"==typeof t)return parseFloat(t);if(Array.isArray(t))return t.map(e);throw new Error("The value should be a number, a parsable string or an array of those.")}},49228:(e,t,r)=>{"use strict";var n=r(44728);function i(e){return Object.keys(e).sort().reduce((function(t,r){return t[r]=e[r],t}),{})}var a={_getQueries:function(e,t){var r=[];return r.push({indexName:e,params:a._getHitsSearchParams(t)}),t.getRefinedDisjunctiveFacets().forEach((function(n){r.push({indexName:e,params:a._getDisjunctiveFacetSearchParams(t,n)})})),t.getRefinedHierarchicalFacets().forEach((function(n){var i=t.getHierarchicalFacetByName(n),s=t.getHierarchicalRefinement(n),c=t._getHierarchicalFacetSeparator(i);if(s.length>0&&s[0].split(c).length>1){var o=s[0].split(c).slice(0,-1).reduce((function(e,t,r){return e.concat({attribute:i.attributes[r],value:0===r?t:[e[e.length-1].value,t].join(c)})}),[]);o.forEach((function(n,s){var c=a._getDisjunctiveFacetSearchParams(t,n.attribute,0===s);function u(e){return i.attributes.some((function(t){return t===e.split(":")[0]}))}var h=(c.facetFilters||[]).reduce((function(e,t){if(Array.isArray(t)){var r=t.filter((function(e){return!u(e)}));r.length>0&&e.push(r)}return"string"!=typeof t||u(t)||e.push(t),e}),[]),l=o[s-1];s>0?c.facetFilters=h.concat(l.attribute+":"+l.value):h.length>0?c.facetFilters=h:delete c.facetFilters,r.push({indexName:e,params:c})}))}})),r},_getHitsSearchParams:function(e){var t=e.facets.concat(e.disjunctiveFacets).concat(a._getHitsHierarchicalFacetsAttributes(e)).sort(),r=a._getFacetFilters(e),s=a._getNumericFilters(e),c=a._getTagFilters(e),o={};return t.length>0&&(o.facets=t.indexOf("*")>-1?["*"]:t),c.length>0&&(o.tagFilters=c),r.length>0&&(o.facetFilters=r),s.length>0&&(o.numericFilters=s),i(n({},e.getQueryParams(),o))},_getDisjunctiveFacetSearchParams:function(e,t,r){var s=a._getFacetFilters(e,t,r),c=a._getNumericFilters(e,t),o=a._getTagFilters(e),u={hitsPerPage:0,page:0,analytics:!1,clickAnalytics:!1};o.length>0&&(u.tagFilters=o);var h=e.getHierarchicalFacetByName(t);return u.facets=h?a._getDisjunctiveHierarchicalFacetAttribute(e,h,r):t,c.length>0&&(u.numericFilters=c),s.length>0&&(u.facetFilters=s),i(n({},e.getQueryParams(),u))},_getNumericFilters:function(e,t){if(e.numericFilters)return e.numericFilters;var r=[];return Object.keys(e.numericRefinements).forEach((function(n){var i=e.numericRefinements[n]||{};Object.keys(i).forEach((function(e){var a=i[e]||[];t!==n&&a.forEach((function(t){if(Array.isArray(t)){var i=t.map((function(t){return n+e+t}));r.push(i)}else r.push(n+e+t)}))}))})),r},_getTagFilters:function(e){return e.tagFilters?e.tagFilters:e.tagRefinements.join(",")},_getFacetFilters:function(e,t,r){var n=[],i=e.facetsRefinements||{};Object.keys(i).sort().forEach((function(e){(i[e]||[]).slice().sort().forEach((function(t){n.push(e+":"+t)}))}));var a=e.facetsExcludes||{};Object.keys(a).sort().forEach((function(e){(a[e]||[]).sort().forEach((function(t){n.push(e+":-"+t)}))}));var s=e.disjunctiveFacetsRefinements||{};Object.keys(s).sort().forEach((function(e){var r=s[e]||[];if(e!==t&&r&&0!==r.length){var i=[];r.slice().sort().forEach((function(t){i.push(e+":"+t)})),n.push(i)}}));var c=e.hierarchicalFacetsRefinements||{};return Object.keys(c).sort().forEach((function(i){var a=(c[i]||[])[0];if(void 0!==a){var s,o,u=e.getHierarchicalFacetByName(i),h=e._getHierarchicalFacetSeparator(u),l=e._getHierarchicalRootPath(u);if(t===i){if(-1===a.indexOf(h)||!l&&!0===r||l&&l.split(h).length===a.split(h).length)return;l?(o=l.split(h).length-1,a=l):(o=a.split(h).length-2,a=a.slice(0,a.lastIndexOf(h))),s=u.attributes[o]}else o=a.split(h).length-1,s=u.attributes[o];s&&n.push([s+":"+a])}})),n},_getHitsHierarchicalFacetsAttributes:function(e){return e.hierarchicalFacets.reduce((function(t,r){var n=e.getHierarchicalRefinement(r.name)[0];if(!n)return t.push(r.attributes[0]),t;var i=e._getHierarchicalFacetSeparator(r),a=n.split(i).length,s=r.attributes.slice(0,a+1);return t.concat(s)}),[])},_getDisjunctiveHierarchicalFacetAttribute:function(e,t,r){var n=e._getHierarchicalFacetSeparator(t);if(!0===r){var i=e._getHierarchicalRootPath(t),a=0;return i&&(a=i.split(n).length),[t.attributes[a]]}var s=(e.getHierarchicalRefinement(t.name)[0]||"").split(n).length-1;return t.attributes.slice(0,s+1)},getSearchForFacetQuery:function(e,t,r,s){var c=s.isDisjunctiveFacet(e)?s.clearRefinements(e):s,o={facetQuery:t,facetName:e};return"number"==typeof r&&(o.maxFacetHits=r),i(n({},a._getHitsSearchParams(c),o))}};e.exports=a},72208:e=>{"use strict";e.exports=function(e){return null!==e&&/^[a-zA-Z0-9_-]{1,64}$/.test(e)}},57749:(e,t,r)=>{"use strict";var n=r(20849),i=r(38657);e.exports=function(e){var t={};return e.forEach((function(e){e.forEach((function(e,r){t[e.objectID]?t[e.objectID]={indexSum:t[e.objectID].indexSum+r,count:t[e.objectID].count+1}:t[e.objectID]={indexSum:r,count:1}}))})),function(e,t){var r=[];return Object.keys(e).forEach((function(n){e[n].count<2&&(e[n].indexSum+=100),r.push({objectID:n,avgOfIndices:e[n].indexSum/t})})),r.sort((function(e,t){return e.avgOfIndices>t.avgOfIndices?1:-1}))}(t,e.length).reduce((function(t,r){var a=n(i(e),(function(e){return e.objectID===r.objectID}));return a?t.concat(a):t}),[])}},16938:e=>{"use strict";e.exports="3.22.3"},83643:function(e){e.exports=function(){"use strict";function e(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function t(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function r(r){for(var n=1;n=0||(i[r]=e[r]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(i[r]=e[r])}return i}function i(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){if(Symbol.iterator in Object(e)||"[object Arguments]"===Object.prototype.toString.call(e)){var r=[],n=!0,i=!1,a=void 0;try{for(var s,c=e[Symbol.iterator]();!(n=(s=c.next()).done)&&(r.push(s.value),!t||r.length!==t);n=!0);}catch(e){i=!0,a=e}finally{try{n||null==c.return||c.return()}finally{if(i)throw a}}return r}}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}function a(e){return function(e){if(Array.isArray(e)){for(var t=0,r=new Array(e.length);t2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}};return Promise.resolve().then((function(){c();var t=JSON.stringify(e);return a()[t]})).then((function(e){return Promise.all([e?e.value:t(),void 0!==e])})).then((function(e){var t=i(e,2),n=t[0],a=t[1];return Promise.all([n,a||r.miss(n)])})).then((function(e){return i(e,1)[0]}))},set:function(e,t){return Promise.resolve().then((function(){var i=a();return i[JSON.stringify(e)]={timestamp:(new Date).getTime(),value:t},n().setItem(r,JSON.stringify(i)),t}))},delete:function(e){return Promise.resolve().then((function(){var t=a();delete t[JSON.stringify(e)],n().setItem(r,JSON.stringify(t))}))},clear:function(){return Promise.resolve().then((function(){n().removeItem(r)}))}}}function c(e){var t=a(e.caches),r=t.shift();return void 0===r?{get:function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}};return t().then((function(e){return Promise.all([e,r.miss(e)])})).then((function(e){return i(e,1)[0]}))},set:function(e,t){return Promise.resolve(t)},delete:function(e){return Promise.resolve()},clear:function(){return Promise.resolve()}}:{get:function(e,n){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}};return r.get(e,n,i).catch((function(){return c({caches:t}).get(e,n,i)}))},set:function(e,n){return r.set(e,n).catch((function(){return c({caches:t}).set(e,n)}))},delete:function(e){return r.delete(e).catch((function(){return c({caches:t}).delete(e)}))},clear:function(){return r.clear().catch((function(){return c({caches:t}).clear()}))}}}function o(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{serializable:!0},t={};return{get:function(r,n){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{miss:function(){return Promise.resolve()}},a=JSON.stringify(r);if(a in t)return Promise.resolve(e.serializable?JSON.parse(t[a]):t[a]);var s=n(),c=i&&i.miss||function(){return Promise.resolve()};return s.then((function(e){return c(e)})).then((function(){return s}))},set:function(r,n){return t[JSON.stringify(r)]=e.serializable?JSON.stringify(n):n,Promise.resolve(n)},delete:function(e){return delete t[JSON.stringify(e)],Promise.resolve()},clear:function(){return t={},Promise.resolve()}}}function u(e){for(var t=e.length-1;t>0;t--){var r=Math.floor(Math.random()*(t+1)),n=e[t];e[t]=e[r],e[r]=n}return e}function h(e,t){return t?(Object.keys(t).forEach((function(r){e[r]=t[r](e)})),e):e}function l(e){for(var t=arguments.length,r=new Array(t>1?t-1:0),n=1;n0?n:void 0,timeout:r.timeout||t,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var d={Read:1,Write:2,Any:3},p=1,g=2,v=3;function y(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:p;return r(r({},e),{},{status:t,lastUpdate:Date.now()})}function R(e){return"string"==typeof e?{protocol:"https",url:e,accept:d.Any}:{protocol:e.protocol||"https",url:e.url,accept:e.accept||d.Any}}var F="GET",b="POST";function _(e,t){return Promise.all(t.map((function(t){return e.get(t,(function(){return Promise.resolve(y(t))}))}))).then((function(e){var r=e.filter((function(e){return function(e){return e.status===p||Date.now()-e.lastUpdate>12e4}(e)})),n=e.filter((function(e){return function(e){return e.status===v&&Date.now()-e.lastUpdate<=12e4}(e)})),i=[].concat(a(r),a(n));return{getTimeout:function(e,t){return(0===n.length&&0===e?1:n.length+3+e)*t},statelessHosts:i.length>0?i.map((function(e){return R(e)})):t}}))}function P(e,t,n,i){var s=[],c=function(e,t){if(e.method!==F&&(void 0!==e.data||void 0!==t.data)){var n=Array.isArray(e.data)?e.data:r(r({},e.data),t.data);return JSON.stringify(n)}}(n,i),o=function(e,t){var n=r(r({},e.headers),t.headers),i={};return Object.keys(n).forEach((function(e){var t=n[e];i[e.toLowerCase()]=t})),i}(e,i),u=n.method,h=n.method!==F?{}:r(r({},n.data),i.data),l=r(r(r({"x-algolia-agent":e.userAgent.value},e.queryParameters),h),i.queryParameters),f=0,m=function t(r,a){var h=r.pop();if(void 0===h)throw{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, please reach out to the Algolia Support team: https://alg.li/support .",transporterStackTrace:O(s)};var m={data:c,headers:o,method:u,url:E(h,n.path,l),connectTimeout:a(f,e.timeouts.connect),responseTimeout:a(f,i.timeout)},d=function(e){var t={request:m,response:e,host:h,triesLeft:r.length};return s.push(t),t},p={onSuccess:function(e){return function(e){try{return JSON.parse(e.content)}catch(t){throw function(e,t){return{name:"DeserializationError",message:e,response:t}}(t.message,e)}}(e)},onRetry:function(n){var i=d(n);return n.isTimedOut&&f++,Promise.all([e.logger.info("Retryable failure",w(i)),e.hostsCache.set(h,y(h,n.isTimedOut?v:g))]).then((function(){return t(r,a)}))},onFail:function(e){throw d(e),function(e,t){var r=e.content,n=e.status,i=r;try{i=JSON.parse(r).message}catch(e){}return function(e,t,r){return{name:"ApiError",message:e,status:t,transporterStackTrace:r}}(i,n,t)}(e,O(s))}};return e.requester.send(m).then((function(e){return function(e,t){return function(e){var t=e.status;return e.isTimedOut||function(e){var t=e.isTimedOut,r=e.status;return!t&&!~~r}(e)||2!=~~(t/100)&&4!=~~(t/100)}(e)?t.onRetry(e):2==~~(e.status/100)?t.onSuccess(e):t.onFail(e)}(e,p)}))};return _(e.hostsCache,t).then((function(e){return m(a(e.statelessHosts).reverse(),e.getTimeout)}))}function j(e){var t={value:"Algolia for JavaScript (".concat(e,")"),add:function(e){var r="; ".concat(e.segment).concat(void 0!==e.version?" (".concat(e.version,")"):"");return-1===t.value.indexOf(r)&&(t.value="".concat(t.value).concat(r)),t}};return t}function E(e,t,r){var n=x(r),i="".concat(e.protocol,"://").concat(e.url,"/").concat("/"===t.charAt(0)?t.substr(1):t);return n.length&&(i+="?".concat(n)),i}function x(e){return Object.keys(e).map((function(t){return l("%s=%s",t,(r=e[t],"[object Object]"===Object.prototype.toString.call(r)||"[object Array]"===Object.prototype.toString.call(r)?JSON.stringify(e[t]):e[t]));var r})).join("&")}function O(e){return e.map((function(e){return w(e)}))}function w(e){var t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return r(r({},e),{},{request:r(r({},e.request),{},{headers:r(r({},e.request.headers),t)})})}var S=function(e){var t=e.appId,n=function(e,t,r){var n={"x-algolia-api-key":r,"x-algolia-application-id":t};return{headers:function(){return e===f.WithinHeaders?n:{}},queryParameters:function(){return e===f.WithinQueryParameters?n:{}}}}(void 0!==e.authMode?e.authMode:f.WithinHeaders,t,e.apiKey),a=function(e){var t=e.hostsCache,r=e.logger,n=e.requester,a=e.requestsCache,s=e.responsesCache,c=e.timeouts,o=e.userAgent,u=e.hosts,h=e.queryParameters,l={hostsCache:t,logger:r,requester:n,requestsCache:a,responsesCache:s,timeouts:c,userAgent:o,headers:e.headers,queryParameters:h,hosts:u.map((function(e){return R(e)})),read:function(e,t){var r=m(t,l.timeouts.read),n=function(){return P(l,l.hosts.filter((function(e){return!!(e.accept&d.Read)})),e,r)};if(!0!==(void 0!==r.cacheable?r.cacheable:e.cacheable))return n();var a={request:e,mappedRequestOptions:r,transporter:{queryParameters:l.queryParameters,headers:l.headers}};return l.responsesCache.get(a,(function(){return l.requestsCache.get(a,(function(){return l.requestsCache.set(a,n()).then((function(e){return Promise.all([l.requestsCache.delete(a),e])}),(function(e){return Promise.all([l.requestsCache.delete(a),Promise.reject(e)])})).then((function(e){var t=i(e,2);return t[0],t[1]}))}))}),{miss:function(e){return l.responsesCache.set(a,e)}})},write:function(e,t){return P(l,l.hosts.filter((function(e){return!!(e.accept&d.Write)})),e,m(t,l.timeouts.write))}};return l}(r(r({hosts:[{url:"".concat(t,"-dsn.algolia.net"),accept:d.Read},{url:"".concat(t,".algolia.net"),accept:d.Write}].concat(u([{url:"".concat(t,"-1.algolianet.com")},{url:"".concat(t,"-2.algolianet.com")},{url:"".concat(t,"-3.algolianet.com")}]))},e),{},{headers:r(r(r({},n.headers()),{"content-type":"application/x-www-form-urlencoded"}),e.headers),queryParameters:r(r({},n.queryParameters()),e.queryParameters)}));return h({transporter:a,appId:t,addAlgoliaAgent:function(e,t){a.userAgent.add({segment:e,version:t})},clearCache:function(){return Promise.all([a.requestsCache.clear(),a.responsesCache.clear()]).then((function(){}))}},e.methods)},A=function(e){return function(t,r){return t.method===F?e.transporter.read(t,r):e.transporter.write(t,r)}},N=function(e){return function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return h({transporter:e.transporter,appId:e.appId,indexName:t},r.methods)}},T=function(e){return function(t,n){var i=t.map((function(e){return r(r({},e),{},{params:x(e.params||{})})}));return e.transporter.read({method:b,path:"1/indexes/*/queries",data:{requests:i},cacheable:!0},n)}},H=function(e){return function(t,i){return Promise.all(t.map((function(t){var a=t.params,s=a.facetName,c=a.facetQuery,o=n(a,["facetName","facetQuery"]);return N(e)(t.indexName,{methods:{searchForFacetValues:I}}).searchForFacetValues(s,c,r(r({},i),o))})))}},Q=function(e){return function(t,r,n){return e.transporter.read({method:b,path:l("1/answers/%s/prediction",e.indexName),data:{query:t,queryLanguages:r},cacheable:!0},n)}},C=function(e){return function(t,r){return e.transporter.read({method:b,path:l("1/indexes/%s/query",e.indexName),data:{query:t},cacheable:!0},r)}},I=function(e){return function(t,r,n){return e.transporter.read({method:b,path:l("1/indexes/%s/facets/%s/query",e.indexName,t),data:{facetQuery:r},cacheable:!0},n)}},k=1,D=2,q=3,L=function(e){return function(t,n){var i=t.map((function(e){return r(r({},e),{},{threshold:e.threshold||0})}));return e.transporter.read({method:b,path:"1/indexes/*/recommendations",data:{requests:i},cacheable:!0},n)}};function V(e,t,n){var i,a={appId:e,apiKey:t,timeouts:{connect:1,read:2,write:30},requester:{send:function(e){return new Promise((function(t){var r=new XMLHttpRequest;r.open(e.method,e.url,!0),Object.keys(e.headers).forEach((function(t){return r.setRequestHeader(t,e.headers[t])}));var n,i=function(e,n){return setTimeout((function(){r.abort(),t({status:0,content:n,isTimedOut:!0})}),1e3*e)},a=i(e.connectTimeout,"Connection timeout");r.onreadystatechange=function(){r.readyState>r.OPENED&&void 0===n&&(clearTimeout(a),n=i(e.responseTimeout,"Socket timeout"))},r.onerror=function(){0===r.status&&(clearTimeout(a),clearTimeout(n),t({content:r.responseText||"Network request failed",status:r.status,isTimedOut:!1}))},r.onload=function(){clearTimeout(a),clearTimeout(n),t({content:r.responseText,status:r.status,isTimedOut:!1})},r.send(e.data)}))}},logger:(i=q,{debug:function(e,t){return k>=i&&console.debug(e,t),Promise.resolve()},info:function(e,t){return D>=i&&console.info(e,t),Promise.resolve()},error:function(e,t){return console.error(e,t),Promise.resolve()}}),responsesCache:o(),requestsCache:o({serializable:!1}),hostsCache:c({caches:[s({key:"".concat("4.24.0","-").concat(e)}),o()]}),userAgent:j("4.24.0").add({segment:"Browser",version:"lite"}),authMode:f.WithinQueryParameters};return S(r(r(r({},a),n),{},{methods:{search:T,searchForFacetValues:H,multipleQueries:T,multipleSearchForFacetValues:H,customRequest:A,initIndex:function(e){return function(t){return N(e)(t,{methods:{search:C,searchForFacetValues:I,findAnswers:Q}})}},getRecommendations:L}}))}return V.version="4.24.0",V}()},29057:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>A});var n=r(96540),i=r(20053),a=r(74103),s=r.n(a),c=r(83643),o=r.n(c),u=r(38193),h=r(5260),l=r(75489),f=r(44070),m=r(44586);const d=["zero","one","two","few","many","other"];function p(e){return d.filter((t=>e.includes(t)))}const g={locale:"en",pluralForms:p(["one","other"]),select:e=>1===e?"one":"other"};function v(){const{i18n:{currentLocale:e}}=(0,m.A)();return(0,n.useMemo)((()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:p(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),g}}),[e])}function y(){const e=v();return{selectMessage:(t,r)=>function(e,t,r){const n=e.split("|");if(1===n.length)return n[0];n.length>r.pluralForms.length&&console.error(`For locale=${r.locale}, a maximum of ${r.pluralForms.length} plural forms are expected (${r.pluralForms.join(",")}), but the message contains ${n.length}: ${e}`);const i=r.select(t),a=r.pluralForms.indexOf(i);return n[Math.min(a,n.length-1)]}(r,t,e)}}var R=r(24255),F=r(89532),b=r(69024),_=r(20481),P=r(21312),j=r(38126),E=r(51062),x=r(98956);const O={searchQueryInput:"searchQueryInput_u2C7",searchVersionInput:"searchVersionInput_m0Ui",searchResultsColumn:"searchResultsColumn_JPFH",algoliaLogo:"algoliaLogo_rT1R",algoliaLogoPathFill:"algoliaLogoPathFill_WdUC",searchResultItem:"searchResultItem_Tv2o",searchResultItemHeading:"searchResultItemHeading_KbCB",searchResultItemPath:"searchResultItemPath_lhe1",searchResultItemSummary:"searchResultItemSummary_AEaO",searchQueryColumn:"searchQueryColumn_RTkw",searchVersionColumn:"searchVersionColumn_ypXd",searchLogoColumn:"searchLogoColumn_rJIA",loadingSpinner:"loadingSpinner_XVxU","loading-spin":"loading-spin_vzvp",loader:"loader_vvXV"};function w(e){let{docsSearchVersionsHelpers:t}=e;const r=Object.entries(t.allDocsData).filter((e=>{let[,t]=e;return t.versions.length>1}));return n.createElement("div",{className:(0,i.A)("col","col--3","padding-left--none",O.searchVersionColumn)},r.map((e=>{let[i,a]=e;const s=r.length>1?`${i}: `:"";return n.createElement("select",{key:i,onChange:e=>t.setSearchVersion(i,e.target.value),defaultValue:t.searchVersions[i],className:O.searchVersionInput},a.versions.map(((e,t)=>n.createElement("option",{key:t,label:`${s}${e.label}`,value:e.name}))))})))}function S(){const{i18n:{currentLocale:e}}=(0,m.A)(),{algolia:{appId:t,apiKey:r,indexName:a}}=(0,j.c)(),c=(0,E.C)(),d=function(){const{selectMessage:e}=y();return t=>e(t,(0,P.T)({id:"theme.SearchPage.documentsFound.plurals",description:'Pluralized label for "{count} documents found". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One document found|{count} documents found"},{count:t}))}(),p=function(){const e=(0,f.Gy)(),[t,r]=(0,n.useState)((()=>Object.entries(e).reduce(((e,t)=>{let[r,n]=t;return{...e,[r]:n.versions[0].name}}),{}))),i=Object.values(e).some((e=>e.versions.length>1));return{allDocsData:e,versioningEnabled:i,searchVersions:t,setSearchVersion:(e,t)=>r((r=>({...r,[e]:t})))}}(),[g,v]=(0,R.b)(),b={items:[],query:null,totalResults:null,totalPages:null,lastPage:null,hasMore:null,loading:null},[S,A]=(0,n.useReducer)(((e,t)=>{switch(t.type){case"reset":return b;case"loading":return{...e,loading:!0};case"update":return g!==t.value.query?e:{...t.value,items:0===t.value.lastPage?t.value.items:e.items.concat(t.value.items)};case"advance":{const t=e.totalPages>e.lastPage+1;return{...e,lastPage:t?e.lastPage+1:e.lastPage,hasMore:t}}default:return e}}),b),N=o()(t,r),T=s()(N,a,{hitsPerPage:15,advancedSyntax:!0,disjunctiveFacets:["language","docusaurus_tag"]});T.on("result",(e=>{let{results:{query:t,hits:r,page:n,nbHits:i,nbPages:a}}=e;if(""===t||!Array.isArray(r))return void A({type:"reset"});const s=e=>e.replace(/algolia-docsearch-suggestion--highlight/g,"search-result-match"),o=r.map((e=>{let{url:t,_highlightResult:{hierarchy:r},_snippetResult:n={}}=e;const i=Object.keys(r).map((e=>s(r[e].value)));return{title:i.pop(),url:c(t),summary:n.content?`${s(n.content.value)}...`:"",breadcrumbs:i}}));A({type:"update",value:{items:o,query:t,totalResults:i,totalPages:a,lastPage:n,hasMore:a>n+1,loading:!1}})}));const[H,Q]=(0,n.useState)(null),C=(0,n.useRef)(0),I=(0,n.useRef)(u.A.canUseIntersectionObserver&&new IntersectionObserver((e=>{const{isIntersecting:t,boundingClientRect:{y:r}}=e[0];t&&C.current>r&&A({type:"advance"}),C.current=r}),{threshold:1})),k=()=>g?(0,P.T)({id:"theme.SearchPage.existingResultsTitle",message:'Search results for "{query}"',description:"The search page title for non-empty query"},{query:g}):(0,P.T)({id:"theme.SearchPage.emptyResultsTitle",message:"Search the documentation",description:"The search page title for empty query"}),D=(0,F._q)((function(t){void 0===t&&(t=0),T.addDisjunctiveFacetRefinement("docusaurus_tag","default"),T.addDisjunctiveFacetRefinement("language",e),Object.entries(p.searchVersions).forEach((e=>{let[t,r]=e;T.addDisjunctiveFacetRefinement("docusaurus_tag",`docs-${t}-${r}`)})),T.setQuery(g).setPage(t).search()}));return(0,n.useEffect)((()=>{if(!H)return;const e=I.current;return e?(e.observe(H),()=>e.unobserve(H)):()=>!0}),[H]),(0,n.useEffect)((()=>{A({type:"reset"}),g&&(A({type:"loading"}),setTimeout((()=>{D()}),300))}),[g,p.searchVersions,D]),(0,n.useEffect)((()=>{S.lastPage&&0!==S.lastPage&&D(S.lastPage)}),[D,S.lastPage]),n.createElement(x.A,null,n.createElement(h.A,null,n.createElement("title",null,(0,_.s)(k())),n.createElement("meta",{property:"robots",content:"noindex, follow"})),n.createElement("div",{className:"container margin-vert--lg"},n.createElement("h1",null,k()),n.createElement("form",{className:"row",onSubmit:e=>e.preventDefault()},n.createElement("div",{className:(0,i.A)("col",O.searchQueryColumn,{"col--9":p.versioningEnabled,"col--12":!p.versioningEnabled})},n.createElement("input",{type:"search",name:"q",className:O.searchQueryInput,placeholder:(0,P.T)({id:"theme.SearchPage.inputPlaceholder",message:"Type your search here",description:"The placeholder for search page input"}),"aria-label":(0,P.T)({id:"theme.SearchPage.inputLabel",message:"Search",description:"The ARIA label for search page input"}),onChange:e=>v(e.target.value),value:g,autoComplete:"off",autoFocus:!0})),p.versioningEnabled&&n.createElement(w,{docsSearchVersionsHelpers:p})),n.createElement("div",{className:"row"},n.createElement("div",{className:(0,i.A)("col","col--8",O.searchResultsColumn)},!!S.totalResults&&d(S.totalResults)),n.createElement("div",{className:(0,i.A)("col","col--4","text--right",O.searchLogoColumn)},n.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://www.algolia.com/","aria-label":(0,P.T)({id:"theme.SearchPage.algoliaLabel",message:"Search by Algolia",description:"The ARIA label for Algolia mention"})},n.createElement("svg",{viewBox:"0 0 168 24",className:O.algoliaLogo},n.createElement("g",{fill:"none"},n.createElement("path",{className:O.algoliaLogoPathFill,d:"M120.925 18.804c-4.386.02-4.386-3.54-4.386-4.106l-.007-13.336 2.675-.424v13.254c0 .322 0 2.358 1.718 2.364v2.248zm-10.846-2.18c.821 0 1.43-.047 1.855-.129v-2.719a6.334 6.334 0 0 0-1.574-.199 5.7 5.7 0 0 0-.897.069 2.699 2.699 0 0 0-.814.24c-.24.116-.439.28-.582.491-.15.212-.219.335-.219.656 0 .628.219.991.616 1.23s.938.362 1.615.362zm-.233-9.7c.883 0 1.629.109 2.231.328.602.218 1.088.525 1.444.915.363.396.609.922.76 1.483.157.56.232 1.175.232 1.85v6.874a32.5 32.5 0 0 1-1.868.314c-.834.123-1.772.185-2.813.185-.69 0-1.327-.069-1.895-.198a4.001 4.001 0 0 1-1.471-.636 3.085 3.085 0 0 1-.951-1.134c-.226-.465-.343-1.12-.343-1.803 0-.656.13-1.073.384-1.525a3.24 3.24 0 0 1 1.047-1.106c.445-.287.95-.492 1.532-.615a8.8 8.8 0 0 1 1.82-.185 8.404 8.404 0 0 1 1.972.24v-.438c0-.307-.035-.6-.11-.874a1.88 1.88 0 0 0-.384-.73 1.784 1.784 0 0 0-.724-.493 3.164 3.164 0 0 0-1.143-.205c-.616 0-1.177.075-1.69.164a7.735 7.735 0 0 0-1.26.307l-.321-2.192c.335-.117.834-.233 1.478-.349a10.98 10.98 0 0 1 2.073-.178zm52.842 9.626c.822 0 1.43-.048 1.854-.13V13.7a6.347 6.347 0 0 0-1.574-.199c-.294 0-.595.021-.896.069a2.7 2.7 0 0 0-.814.24 1.46 1.46 0 0 0-.582.491c-.15.212-.218.335-.218.656 0 .628.218.991.615 1.23.404.245.938.362 1.615.362zm-.226-9.694c.883 0 1.629.108 2.231.327.602.219 1.088.526 1.444.915.355.39.609.923.759 1.483a6.8 6.8 0 0 1 .233 1.852v6.873c-.41.088-1.034.19-1.868.314-.834.123-1.772.184-2.813.184-.69 0-1.327-.068-1.895-.198a4.001 4.001 0 0 1-1.471-.635 3.085 3.085 0 0 1-.951-1.134c-.226-.465-.343-1.12-.343-1.804 0-.656.13-1.073.384-1.524.26-.45.608-.82 1.047-1.107.445-.286.95-.491 1.532-.614a8.803 8.803 0 0 1 2.751-.13c.329.034.671.096 1.04.185v-.437a3.3 3.3 0 0 0-.109-.875 1.873 1.873 0 0 0-.384-.731 1.784 1.784 0 0 0-.724-.492 3.165 3.165 0 0 0-1.143-.205c-.616 0-1.177.075-1.69.164a7.75 7.75 0 0 0-1.26.307l-.321-2.193c.335-.116.834-.232 1.478-.348a11.633 11.633 0 0 1 2.073-.177zm-8.034-1.271a1.626 1.626 0 0 1-1.628-1.62c0-.895.725-1.62 1.628-1.62.904 0 1.63.725 1.63 1.62 0 .895-.733 1.62-1.63 1.62zm1.348 13.22h-2.689V7.27l2.69-.423v11.956zm-4.714 0c-4.386.02-4.386-3.54-4.386-4.107l-.008-13.336 2.676-.424v13.254c0 .322 0 2.358 1.718 2.364v2.248zm-8.698-5.903c0-1.156-.253-2.119-.746-2.788-.493-.677-1.183-1.01-2.067-1.01-.882 0-1.574.333-2.065 1.01-.493.676-.733 1.632-.733 2.788 0 1.168.246 1.953.74 2.63.492.683 1.183 1.018 2.066 1.018.882 0 1.574-.342 2.067-1.019.492-.683.738-1.46.738-2.63zm2.737-.007c0 .902-.13 1.584-.397 2.33a5.52 5.52 0 0 1-1.128 1.906 4.986 4.986 0 0 1-1.752 1.223c-.685.286-1.739.45-2.265.45-.528-.006-1.574-.157-2.252-.45a5.096 5.096 0 0 1-1.744-1.223c-.487-.527-.863-1.162-1.137-1.906a6.345 6.345 0 0 1-.41-2.33c0-.902.123-1.77.397-2.508a5.554 5.554 0 0 1 1.15-1.892 5.133 5.133 0 0 1 1.75-1.216c.679-.287 1.425-.423 2.232-.423.808 0 1.553.142 2.237.423a4.88 4.88 0 0 1 1.753 1.216 5.644 5.644 0 0 1 1.135 1.892c.287.738.431 1.606.431 2.508zm-20.138 0c0 1.12.246 2.363.738 2.882.493.52 1.13.78 1.91.78.424 0 .828-.062 1.204-.178.377-.116.677-.253.917-.417V9.33a10.476 10.476 0 0 0-1.766-.226c-.971-.028-1.71.37-2.23 1.004-.513.636-.773 1.75-.773 2.788zm7.438 5.274c0 1.824-.466 3.156-1.404 4.004-.936.846-2.367 1.27-4.296 1.27-.705 0-2.17-.137-3.34-.396l.431-2.118c.98.205 2.272.26 2.95.26 1.074 0 1.84-.219 2.299-.656.459-.437.684-1.086.684-1.948v-.437a8.07 8.07 0 0 1-1.047.397c-.43.13-.93.198-1.492.198-.739 0-1.41-.116-2.018-.349a4.206 4.206 0 0 1-1.567-1.025c-.431-.45-.774-1.017-1.013-1.694-.24-.677-.363-1.885-.363-2.773 0-.834.13-1.88.384-2.577.26-.696.629-1.298 1.129-1.796.493-.498 1.095-.881 1.8-1.162a6.605 6.605 0 0 1 2.428-.457c.87 0 1.67.109 2.45.24.78.129 1.444.265 1.985.415V18.17zM6.972 6.677v1.627c-.712-.446-1.52-.67-2.425-.67-.585 0-1.045.13-1.38.391a1.24 1.24 0 0 0-.502 1.03c0 .425.164.765.494 1.02.33.256.835.532 1.516.83.447.192.795.356 1.045.495.25.138.537.332.862.582.324.25.563.548.718.894.154.345.23.741.23 1.188 0 .947-.334 1.691-1.004 2.234-.67.542-1.537.814-2.601.814-1.18 0-2.16-.229-2.936-.686v-1.708c.84.628 1.814.942 2.92.942.585 0 1.048-.136 1.388-.407.34-.271.51-.646.51-1.125 0-.287-.1-.55-.302-.79-.203-.24-.42-.42-.655-.542-.234-.123-.585-.29-1.053-.503a61.27 61.27 0 0 1-.582-.271 13.67 13.67 0 0 1-.55-.287 4.275 4.275 0 0 1-.567-.351 6.92 6.92 0 0 1-.455-.4c-.18-.17-.31-.34-.39-.51-.08-.17-.155-.37-.224-.598a2.553 2.553 0 0 1-.104-.742c0-.915.333-1.638.998-2.17.664-.532 1.523-.798 2.576-.798.968 0 1.793.17 2.473.51zm7.468 5.696v-.287c-.022-.607-.187-1.088-.495-1.444-.309-.357-.75-.535-1.324-.535-.532 0-.99.194-1.373.583-.382.388-.622.949-.717 1.683h3.909zm1.005 2.792v1.404c-.596.34-1.383.51-2.362.51-1.255 0-2.255-.377-3-1.132-.744-.755-1.116-1.744-1.116-2.968 0-1.297.34-2.316 1.021-3.055.68-.74 1.548-1.11 2.6-1.11 1.033 0 1.852.323 2.458.966.606.644.91 1.572.91 2.784 0 .33-.033.676-.096 1.038h-5.314c.107.702.405 1.239.894 1.611.49.372 1.106.558 1.85.558.862 0 1.58-.202 2.155-.606zm6.605-1.77h-1.212c-.596 0-1.045.116-1.349.35-.303.234-.454.532-.454.894 0 .372.117.664.35.877.235.213.575.32 1.022.32.51 0 .912-.142 1.204-.424.293-.281.44-.651.44-1.108v-.91zm-4.068-2.554V9.325c.627-.361 1.457-.542 2.489-.542 2.116 0 3.175 1.026 3.175 3.08V17h-1.548v-.957c-.415.68-1.143 1.02-2.186 1.02-.766 0-1.38-.22-1.843-.661-.462-.442-.694-1.003-.694-1.684 0-.776.293-1.38.878-1.81.585-.431 1.404-.647 2.457-.647h1.34V11.8c0-.554-.133-.971-.399-1.253-.266-.282-.707-.423-1.324-.423a4.07 4.07 0 0 0-2.345.718zm9.333-1.93v1.42c.394-1 1.101-1.5 2.123-1.5.148 0 .313.016.494.048v1.531a1.885 1.885 0 0 0-.75-.143c-.542 0-.989.24-1.34.718-.351.479-.527 1.048-.527 1.707V17h-1.563V8.91h1.563zm5.01 4.084c.022.82.272 1.492.75 2.019.479.526 1.15.79 2.01.79.639 0 1.235-.176 1.788-.527v1.404c-.521.319-1.186.479-1.995.479-1.265 0-2.276-.4-3.031-1.197-.755-.798-1.133-1.792-1.133-2.984 0-1.16.38-2.151 1.14-2.975.761-.825 1.79-1.237 3.088-1.237.702 0 1.346.149 1.93.447v1.436a3.242 3.242 0 0 0-1.77-.495c-.84 0-1.513.266-2.019.798-.505.532-.758 1.213-.758 2.042zM40.24 5.72v4.579c.458-1 1.293-1.5 2.505-1.5.787 0 1.42.245 1.899.734.479.49.718 1.17.718 2.042V17h-1.564v-5.106c0-.553-.14-.98-.422-1.284-.282-.303-.652-.455-1.11-.455-.531 0-1.002.202-1.411.606-.41.405-.615 1.022-.615 1.851V17h-1.563V5.72h1.563zm14.966 10.02c.596 0 1.096-.253 1.5-.758.404-.506.606-1.157.606-1.955 0-.915-.202-1.62-.606-2.114-.404-.495-.92-.742-1.548-.742-.553 0-1.05.224-1.491.67-.442.447-.662 1.133-.662 2.058 0 .958.212 1.67.638 2.138.425.469.946.703 1.563.703zM53.004 5.72v4.42c.574-.894 1.388-1.341 2.44-1.341 1.022 0 1.857.383 2.506 1.149.649.766.973 1.781.973 3.047 0 1.138-.309 2.109-.925 2.912-.617.803-1.463 1.205-2.537 1.205-1.075 0-1.894-.447-2.457-1.34V17h-1.58V5.72h1.58zm9.908 11.104l-3.223-7.913h1.739l1.005 2.632 1.26 3.415c.096-.32.48-1.458 1.15-3.415l.909-2.632h1.66l-2.92 7.866c-.777 2.074-1.963 3.11-3.559 3.11a2.92 2.92 0 0 1-.734-.079v-1.34c.17.042.351.064.543.064 1.032 0 1.755-.57 2.17-1.708z"}),n.createElement("path",{fill:"#5468FF",d:"M78.988.938h16.594a2.968 2.968 0 0 1 2.966 2.966V20.5a2.967 2.967 0 0 1-2.966 2.964H78.988a2.967 2.967 0 0 1-2.966-2.964V3.897A2.961 2.961 0 0 1 78.988.938z"}),n.createElement("path",{fill:"white",d:"M89.632 5.967v-.772a.978.978 0 0 0-.978-.977h-2.28a.978.978 0 0 0-.978.977v.793c0 .088.082.15.171.13a7.127 7.127 0 0 1 1.984-.28c.65 0 1.295.088 1.917.259.082.02.164-.04.164-.13m-6.248 1.01l-.39-.389a.977.977 0 0 0-1.382 0l-.465.465a.973.973 0 0 0 0 1.38l.383.383c.062.061.15.047.205-.014.226-.307.472-.601.746-.874.281-.28.568-.526.883-.751.068-.042.075-.137.02-.2m4.16 2.453v3.341c0 .096.104.165.192.117l2.97-1.537c.068-.034.089-.117.055-.184a3.695 3.695 0 0 0-3.08-1.866c-.068 0-.136.054-.136.13m0 8.048a4.489 4.489 0 0 1-4.49-4.482 4.488 4.488 0 0 1 4.49-4.482 4.488 4.488 0 0 1 4.489 4.482 4.484 4.484 0 0 1-4.49 4.482m0-10.85a6.363 6.363 0 1 0 0 12.729 6.37 6.37 0 0 0 6.372-6.368 6.358 6.358 0 0 0-6.371-6.36"})))))),S.items.length>0?n.createElement("main",null,S.items.map(((e,t)=>{let{title:r,url:a,summary:s,breadcrumbs:c}=e;return n.createElement("article",{key:t,className:O.searchResultItem},n.createElement("h2",{className:O.searchResultItemHeading},n.createElement(l.A,{to:a,dangerouslySetInnerHTML:{__html:r}})),c.length>0&&n.createElement("nav",{"aria-label":"breadcrumbs"},n.createElement("ul",{className:(0,i.A)("breadcrumbs",O.searchResultItemPath)},c.map(((e,t)=>n.createElement("li",{key:t,className:"breadcrumbs__item",dangerouslySetInnerHTML:{__html:e}}))))),s&&n.createElement("p",{className:O.searchResultItemSummary,dangerouslySetInnerHTML:{__html:s}}))}))):[g&&!S.loading&&n.createElement("p",{key:"no-results"},n.createElement(P.A,{id:"theme.SearchPage.noResultsText",description:"The paragraph for empty search result"},"No results were found")),!!S.loading&&n.createElement("div",{key:"spinner",className:O.loadingSpinner})],S.hasMore&&n.createElement("div",{className:O.loader,ref:Q},n.createElement(P.A,{id:"theme.SearchPage.fetchingNewResults",description:"The paragraph for fetching new search results"},"Fetching new results..."))))}function A(){return n.createElement(b.e3,{className:"search-page-wrapper"},n.createElement(S,null))}}}]); \ No newline at end of file diff --git a/assets/js/1a4e3797.491d505b.js.LICENSE.txt b/assets/js/1a4e3797.491d505b.js.LICENSE.txt new file mode 100644 index 0000000000..bfc7620fe3 --- /dev/null +++ b/assets/js/1a4e3797.491d505b.js.LICENSE.txt @@ -0,0 +1 @@ +/*! algoliasearch-lite.umd.js | 4.24.0 | © Algolia, inc. | https://github.com/algolia/algoliasearch-client-javascript */ diff --git a/assets/js/1aa05129.30e43841.js b/assets/js/1aa05129.8d91a7ea.js similarity index 89% rename from assets/js/1aa05129.30e43841.js rename to assets/js/1aa05129.8d91a7ea.js index 7a0eb9e52f..cf01e67f9c 100644 --- a/assets/js/1aa05129.30e43841.js +++ b/assets/js/1aa05129.8d91a7ea.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2207],{13970:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>u});var i=n(58168),r=(n(96540),n(15680));n(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-6.0/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories.",source:"@site/versioned_docs/version-6.0/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/6.0/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/implementing-security.md",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},sidebar:"docs",previous:{title:"Fine grained security",permalink:"/docs/6.0/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/6.0/query-plan"}},c={},u=[],l={toc:u},p="wrapper";function h(e){let{components:t,...n}=e;return(0,r.yg)(p,(0,i.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories."),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2207],{13970:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),r=(i(96540),i(15680));i(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-6.0/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories.",source:"@site/versioned_docs/version-6.0/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/6.0/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/implementing-security.md",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},sidebar:"docs",previous:{title:"Fine grained security",permalink:"/docs/6.0/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/6.0/query-plan"}},c={},l=[],u={toc:l},p="wrapper";function h(e){let{components:t,...i}=e;return(0,r.yg)(p,(0,n.A)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories."),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1af245cd.0b878aaa.js b/assets/js/1af245cd.ceaf76e0.js similarity index 93% rename from assets/js/1af245cd.0b878aaa.js rename to assets/js/1af245cd.ceaf76e0.js index aa5d200b33..dd9b81c810 100644 --- a/assets/js/1af245cd.0b878aaa.js +++ b/assets/js/1af245cd.ceaf76e0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2965],{19365:(t,e,n)=>{n.d(e,{A:()=>i});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function i(t){let{children:e,hidden:n,className:i}=t;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,i),hidden:n},e)}},11470:(t,e,n)=>{n.d(e,{A:()=>w});var a=n(58168),r=n(96540),o=n(20053),i=n(23104),s=n(56347),l=n(57485),u=n(31682),c=n(89466);function p(t){return function(t){return r.Children.map(t,(t=>{if(!t||(0,r.isValidElement)(t)&&function(t){const{props:e}=t;return!!e&&"object"==typeof e&&"value"in e}(t))return t;throw new Error(`Docusaurus error: Bad child <${"string"==typeof t.type?t.type:t.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(t).map((t=>{let{props:{value:e,label:n,attributes:a,default:r}}=t;return{value:e,label:n,attributes:a,default:r}}))}function d(t){const{values:e,children:n}=t;return(0,r.useMemo)((()=>{const t=e??p(n);return function(t){const e=(0,u.X)(t,((t,e)=>t.value===e.value));if(e.length>0)throw new Error(`Docusaurus error: Duplicate values "${e.map((t=>t.value)).join(", ")}" found in . Every value needs to be unique.`)}(t),t}),[e,n])}function g(t){let{value:e,tabValues:n}=t;return n.some((t=>t.value===e))}function h(t){let{queryString:e=!1,groupId:n}=t;const a=(0,s.W6)(),o=function(t){let{queryString:e=!1,groupId:n}=t;if("string"==typeof e)return e;if(!1===e)return null;if(!0===e&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:e,groupId:n});return[(0,l.aZ)(o),(0,r.useCallback)((t=>{if(!o)return;const e=new URLSearchParams(a.location.search);e.set(o,t),a.replace({...a.location,search:e.toString()})}),[o,a])]}function b(t){const{defaultValue:e,queryString:n=!1,groupId:a}=t,o=d(t),[i,s]=(0,r.useState)((()=>function(t){let{defaultValue:e,tabValues:n}=t;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(e){if(!g({value:e,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${e}" but none of its children has the corresponding value. Available values are: ${n.map((t=>t.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return e}const a=n.find((t=>t.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:e,tabValues:o}))),[l,u]=h({queryString:n,groupId:a}),[p,b]=function(t){let{groupId:e}=t;const n=function(t){return t?`docusaurus.tab.${t}`:null}(e),[a,o]=(0,c.Dv)(n);return[a,(0,r.useCallback)((t=>{n&&o.set(t)}),[n,o])]}({groupId:a}),m=(()=>{const t=l??p;return g({value:t,tabValues:o})?t:null})();(0,r.useLayoutEffect)((()=>{m&&s(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((t=>{if(!g({value:t,tabValues:o}))throw new Error(`Can't select invalid tab value=${t}`);s(t),u(t),b(t)}),[u,b,o]),tabValues:o}}var m=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(t){let{className:e,block:n,selectedValue:s,selectValue:l,tabValues:u}=t;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),d=t=>{const e=t.currentTarget,n=c.indexOf(e),a=u[n].value;a!==s&&(p(e),l(a))},g=t=>{let e=null;switch(t.key){case"Enter":d(t);break;case"ArrowRight":{const n=c.indexOf(t.currentTarget)+1;e=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(t.currentTarget)-1;e=c[n]??c[c.length-1];break}}e?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},e)},u.map((t=>{let{value:e,label:n,attributes:i}=t;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:s===e?0:-1,"aria-selected":s===e,key:e,ref:t=>c.push(t),onKeyDown:g,onClick:d},i,{className:(0,o.A)("tabs__item",y.tabItem,i?.className,{"tabs__item--active":s===e})}),n??e)})))}function A(t){let{lazy:e,children:n,selectedValue:a}=t;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(e){const t=o.find((t=>t.props.value===a));return t?(0,r.cloneElement)(t,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((t,e)=>(0,r.cloneElement)(t,{key:e,hidden:t.props.value!==a}))))}function v(t){const e=b(t);return r.createElement("div",{className:(0,o.A)("tabs-container",y.tabList)},r.createElement(f,(0,a.A)({},t,e)),r.createElement(A,(0,a.A)({},t,e)))}function w(t){const e=(0,m.A)();return r.createElement(v,(0,a.A)({key:String(e)},t))}},62002:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>c,contentTitle:()=>l,default:()=>h,frontMatter:()=>s,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),o=(n(67443),n(11470)),i=n(19365);const s={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},l=void 0,u={unversionedId:"doctrine-annotations-attributes",id:"version-7.0.0/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-7.0.0/doctrine-annotations-attributes.mdx",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/doctrine-annotations-attributes.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},sidebar:"docs",previous:{title:"Migrating",permalink:"/docs/migrating"},next:{title:"Annotations reference",permalink:"/docs/annotations-reference"}},c={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2},{value:"Migrating from Doctrine annotations to PHP 8 attributes",id:"migrating-from-doctrine-annotations-to-php-8-attributes",level:2}],d={toc:p},g="wrapper";function h(t){let{components:e,...n}=t;return(0,r.yg)(g,(0,a.A)({},d,n,{components:e,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,r.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in a future release."),(0,r.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support. This was the purpose of the ',(0,r.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,r.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"Please note that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,r.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,r.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,r.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("strong",null,"Heads up!"),(0,r.yg)("p",null,"Some IDEs provide support for Doctrine annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",null,"PhpStorm via the ",(0,r.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,r.yg)("li",null,"Eclipse via the ",(0,r.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,r.yg)("li",null,"Netbeans has native support")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"We strongly recommend using an IDE that has Doctrine annotations support.\n"))),(0,r.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,r.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,r.yg)("p",null,"The same code can be written this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,r.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,r.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,r.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,r.yg)("p",null,"They support the same attributes too."),(0,r.yg)("p",null,"A few notable differences:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,r.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,r.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,r.yg)("p",null,"Let's take an example with the ",(0,r.yg)("a",{parentName:"p",href:"/docs/autowiring"},(0,r.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')))),(0,r.yg)("h2",{id:"migrating-from-doctrine-annotations-to-php-8-attributes"},"Migrating from Doctrine annotations to PHP 8 attributes"),(0,r.yg)("p",null,"The good news is that you can easily migrate from Doctrine annotations to PHP 8 attributes using the amazing, ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/rectorphp/rector"},"Rector library"),". To do so, you'll want to use the following rector configuration:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="rector.php"',title:'"rector.php"'},"import(SetList::CODE_QUALITY);\n\n // Set parameters\n $parameters = $containerConfigurator->parameters();\n $parameters->set(Option::PATHS, [\n __DIR__ . '/src',\n __DIR__ . '/tests',\n ]);\n\n $services = $containerConfigurator->services();\n\n // @Validate and @Assertion are part of other libraries, include if necessary\n $services->set(AnnotationToAttributeRector::class)\n ->configure([\n new AnnotationToAttribute(GraphQLite\\Query::class),\n new AnnotationToAttribute(GraphQLite\\Mutation::class),\n new AnnotationToAttribute(GraphQLite\\Type::class),\n new AnnotationToAttribute(GraphQLite\\ExtendType::class),\n new AnnotationToAttribute(GraphQLite\\Input::class),\n new AnnotationToAttribute(GraphQLite\\Field::class),\n new AnnotationToAttribute(GraphQLite\\SourceField::class),\n new AnnotationToAttribute(GraphQLite\\MagicField::class),\n new AnnotationToAttribute(GraphQLite\\Logged::class),\n new AnnotationToAttribute(GraphQLite\\Right::class),\n new AnnotationToAttribute(GraphQLite\\FailWith::class),\n new AnnotationToAttribute(GraphQLite\\HideIfUnauthorized::class),\n new AnnotationToAttribute(GraphQLite\\InjectUser::class),\n new AnnotationToAttribute(GraphQLite\\Security::class),\n new AnnotationToAttribute(GraphQLite\\Factory::class),\n new AnnotationToAttribute(GraphQLite\\UseInputType::class),\n new AnnotationToAttribute(GraphQLite\\Decorate::class),\n new AnnotationToAttribute(GraphQLite\\Autowire::class),\n new AnnotationToAttribute(GraphQLite\\HideParameter::class),\n new AnnotationToAttribute(GraphQLite\\EnumType::class),\n ]);\n};\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2965],{19365:(t,e,n)=>{n.d(e,{A:()=>i});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function i(t){let{children:e,hidden:n,className:i}=t;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,i),hidden:n},e)}},11470:(t,e,n)=>{n.d(e,{A:()=>w});var a=n(58168),r=n(96540),o=n(20053),i=n(23104),s=n(56347),l=n(57485),u=n(31682),c=n(89466);function p(t){return function(t){return r.Children.map(t,(t=>{if(!t||(0,r.isValidElement)(t)&&function(t){const{props:e}=t;return!!e&&"object"==typeof e&&"value"in e}(t))return t;throw new Error(`Docusaurus error: Bad child <${"string"==typeof t.type?t.type:t.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(t).map((t=>{let{props:{value:e,label:n,attributes:a,default:r}}=t;return{value:e,label:n,attributes:a,default:r}}))}function d(t){const{values:e,children:n}=t;return(0,r.useMemo)((()=>{const t=e??p(n);return function(t){const e=(0,u.X)(t,((t,e)=>t.value===e.value));if(e.length>0)throw new Error(`Docusaurus error: Duplicate values "${e.map((t=>t.value)).join(", ")}" found in . Every value needs to be unique.`)}(t),t}),[e,n])}function h(t){let{value:e,tabValues:n}=t;return n.some((t=>t.value===e))}function g(t){let{queryString:e=!1,groupId:n}=t;const a=(0,s.W6)(),o=function(t){let{queryString:e=!1,groupId:n}=t;if("string"==typeof e)return e;if(!1===e)return null;if(!0===e&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:e,groupId:n});return[(0,l.aZ)(o),(0,r.useCallback)((t=>{if(!o)return;const e=new URLSearchParams(a.location.search);e.set(o,t),a.replace({...a.location,search:e.toString()})}),[o,a])]}function b(t){const{defaultValue:e,queryString:n=!1,groupId:a}=t,o=d(t),[i,s]=(0,r.useState)((()=>function(t){let{defaultValue:e,tabValues:n}=t;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(e){if(!h({value:e,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${e}" but none of its children has the corresponding value. Available values are: ${n.map((t=>t.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return e}const a=n.find((t=>t.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:e,tabValues:o}))),[l,u]=g({queryString:n,groupId:a}),[p,b]=function(t){let{groupId:e}=t;const n=function(t){return t?`docusaurus.tab.${t}`:null}(e),[a,o]=(0,c.Dv)(n);return[a,(0,r.useCallback)((t=>{n&&o.set(t)}),[n,o])]}({groupId:a}),m=(()=>{const t=l??p;return h({value:t,tabValues:o})?t:null})();(0,r.useLayoutEffect)((()=>{m&&s(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((t=>{if(!h({value:t,tabValues:o}))throw new Error(`Can't select invalid tab value=${t}`);s(t),u(t),b(t)}),[u,b,o]),tabValues:o}}var m=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(t){let{className:e,block:n,selectedValue:s,selectValue:l,tabValues:u}=t;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),d=t=>{const e=t.currentTarget,n=c.indexOf(e),a=u[n].value;a!==s&&(p(e),l(a))},h=t=>{let e=null;switch(t.key){case"Enter":d(t);break;case"ArrowRight":{const n=c.indexOf(t.currentTarget)+1;e=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(t.currentTarget)-1;e=c[n]??c[c.length-1];break}}e?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},e)},u.map((t=>{let{value:e,label:n,attributes:i}=t;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:s===e?0:-1,"aria-selected":s===e,key:e,ref:t=>c.push(t),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",y.tabItem,i?.className,{"tabs__item--active":s===e})}),n??e)})))}function A(t){let{lazy:e,children:n,selectedValue:a}=t;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(e){const t=o.find((t=>t.props.value===a));return t?(0,r.cloneElement)(t,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((t,e)=>(0,r.cloneElement)(t,{key:e,hidden:t.props.value!==a}))))}function v(t){const e=b(t);return r.createElement("div",{className:(0,o.A)("tabs-container",y.tabList)},r.createElement(f,(0,a.A)({},t,e)),r.createElement(A,(0,a.A)({},t,e)))}function w(t){const e=(0,m.A)();return r.createElement(v,(0,a.A)({key:String(e)},t))}},62002:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>c,contentTitle:()=>l,default:()=>g,frontMatter:()=>s,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),o=(n(67443),n(11470)),i=n(19365);const s={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},l=void 0,u={unversionedId:"doctrine-annotations-attributes",id:"version-7.0.0/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-7.0.0/doctrine-annotations-attributes.mdx",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/doctrine-annotations-attributes.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},sidebar:"docs",previous:{title:"Migrating",permalink:"/docs/migrating"},next:{title:"Annotations reference",permalink:"/docs/annotations-reference"}},c={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2},{value:"Migrating from Doctrine annotations to PHP 8 attributes",id:"migrating-from-doctrine-annotations-to-php-8-attributes",level:2}],d={toc:p},h="wrapper";function g(t){let{components:e,...n}=t;return(0,r.yg)(h,(0,a.A)({},d,n,{components:e,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,r.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in a future release."),(0,r.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support. This was the purpose of the ',(0,r.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,r.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"Please note that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,r.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,r.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,r.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("strong",null,"Heads up!"),(0,r.yg)("p",null,"Some IDEs provide support for Doctrine annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",null,"PhpStorm via the ",(0,r.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,r.yg)("li",null,"Eclipse via the ",(0,r.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,r.yg)("li",null,"Netbeans has native support")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"We strongly recommend using an IDE that has Doctrine annotations support.\n"))),(0,r.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,r.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,r.yg)("p",null,"The same code can be written this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,r.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,r.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,r.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,r.yg)("p",null,"They support the same attributes too."),(0,r.yg)("p",null,"A few notable differences:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,r.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,r.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,r.yg)("p",null,"Let's take an example with the ",(0,r.yg)("a",{parentName:"p",href:"/docs/autowiring"},(0,r.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')))),(0,r.yg)("h2",{id:"migrating-from-doctrine-annotations-to-php-8-attributes"},"Migrating from Doctrine annotations to PHP 8 attributes"),(0,r.yg)("p",null,"The good news is that you can easily migrate from Doctrine annotations to PHP 8 attributes using the amazing, ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/rectorphp/rector"},"Rector library"),". To do so, you'll want to use the following rector configuration:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="rector.php"',title:'"rector.php"'},"import(SetList::CODE_QUALITY);\n\n // Set parameters\n $parameters = $containerConfigurator->parameters();\n $parameters->set(Option::PATHS, [\n __DIR__ . '/src',\n __DIR__ . '/tests',\n ]);\n\n $services = $containerConfigurator->services();\n\n // @Validate and @Assertion are part of other libraries, include if necessary\n $services->set(AnnotationToAttributeRector::class)\n ->configure([\n new AnnotationToAttribute(GraphQLite\\Query::class),\n new AnnotationToAttribute(GraphQLite\\Mutation::class),\n new AnnotationToAttribute(GraphQLite\\Type::class),\n new AnnotationToAttribute(GraphQLite\\ExtendType::class),\n new AnnotationToAttribute(GraphQLite\\Input::class),\n new AnnotationToAttribute(GraphQLite\\Field::class),\n new AnnotationToAttribute(GraphQLite\\SourceField::class),\n new AnnotationToAttribute(GraphQLite\\MagicField::class),\n new AnnotationToAttribute(GraphQLite\\Logged::class),\n new AnnotationToAttribute(GraphQLite\\Right::class),\n new AnnotationToAttribute(GraphQLite\\FailWith::class),\n new AnnotationToAttribute(GraphQLite\\HideIfUnauthorized::class),\n new AnnotationToAttribute(GraphQLite\\InjectUser::class),\n new AnnotationToAttribute(GraphQLite\\Security::class),\n new AnnotationToAttribute(GraphQLite\\Factory::class),\n new AnnotationToAttribute(GraphQLite\\UseInputType::class),\n new AnnotationToAttribute(GraphQLite\\Decorate::class),\n new AnnotationToAttribute(GraphQLite\\Autowire::class),\n new AnnotationToAttribute(GraphQLite\\HideParameter::class),\n new AnnotationToAttribute(GraphQLite\\EnumType::class),\n ]);\n};\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1b1927f4.70837fdd.js b/assets/js/1b1927f4.999e377f.js similarity index 52% rename from assets/js/1b1927f4.70837fdd.js rename to assets/js/1b1927f4.999e377f.js index 991af2a35f..d4d97e5177 100644 --- a/assets/js/1b1927f4.70837fdd.js +++ b/assets/js/1b1927f4.999e377f.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5099],{9717:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>p,default:()=>y,frontMatter:()=>l,metadata:()=>o,toc:()=>u});var a=n(58168),i=(n(96540),n(15680));n(67443);const l={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},p=void 0,o={unversionedId:"multiple-output-types",id:"version-6.1/multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.1/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/6.1/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/multiple-output-types.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"docs",previous:{title:"Extending an input type",permalink:"/docs/6.1/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/6.1/symfony-bundle-advanced"}},s={},u=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],d={toc:u},r="wrapper";function y(e){let{components:t,...n}=e;return(0,i.yg)(r,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("small",null,"Available in GraphQLite 4.0+"),(0,i.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,i.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,i.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,i.yg)("h2",{id:"example"},"Example"),(0,i.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n')),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,i.yg)("a",{parentName:"p",href:"/docs/6.1/external-type-declaration"},'"external" type')," mapping the ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,i.yg)("p",null,"First of all, we specify ",(0,i.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,i.yg)("p",null,"Then, we specify ",(0,i.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,i.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,i.yg)("p",null,"Finally, we can write our requests:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n')),(0,i.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,i.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,i.yg)("p",null,"Is a result, when the end user calls the ",(0,i.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,i.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,i.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,i.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,i.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,i.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,i.yg)("p",null,"If you want to extend a type using the ",(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,i.yg)("p",null,"So instead of writing:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n")),(0,i.yg)("p",null,"you will write:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n')),(0,i.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5099],{9717:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>p,default:()=>y,frontMatter:()=>i,metadata:()=>o,toc:()=>u});var a=n(58168),l=(n(96540),n(15680));n(67443);const i={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},p=void 0,o={unversionedId:"multiple-output-types",id:"version-6.1/multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.1/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/6.1/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/multiple-output-types.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"docs",previous:{title:"Extending an input type",permalink:"/docs/6.1/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/6.1/symfony-bundle-advanced"}},s={},u=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],d={toc:u},r="wrapper";function y(e){let{components:t,...n}=e;return(0,l.yg)(r,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n')),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/external-type-declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n')),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n")),(0,l.yg)("p",null,"you will write:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n')),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1ba75d10.077c9e1f.js b/assets/js/1ba75d10.f25708bf.js similarity index 98% rename from assets/js/1ba75d10.077c9e1f.js rename to assets/js/1ba75d10.f25708bf.js index 36910d1ac5..9af680ee46 100644 --- a/assets/js/1ba75d10.077c9e1f.js +++ b/assets/js/1ba75d10.f25708bf.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8470],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const o={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),o=a(20053),u=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=p(e),[u,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,o]),tabValues:o}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,o.A)("tabs__item",v.tabItem,u?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function T(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},61366:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),o=(a(67443),a(11470)),u=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,i={unversionedId:"mutations",id:"version-4.2/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-4.2/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/4.2/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/mutations.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"version-4.2/docs",previous:{title:"Queries",permalink:"/docs/4.2/queries"},next:{title:"Type mapping",permalink:"/docs/4.2/type-mapping"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8470],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const o={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),o=a(20053),u=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=p(e),[u,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,o]),tabValues:o}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,o.A)("tabs__item",v.tabItem,u?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function T(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},61366:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),o=(a(67443),a(11470)),u=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,i={unversionedId:"mutations",id:"version-4.2/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-4.2/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/4.2/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/mutations.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"version-4.2/docs",previous:{title:"Queries",permalink:"/docs/4.2/queries"},next:{title:"Type mapping",permalink:"/docs/4.2/type-mapping"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1be78505.9bd92206.js b/assets/js/1be78505.f41f285f.js similarity index 98% rename from assets/js/1be78505.9bd92206.js rename to assets/js/1be78505.f41f285f.js index 5ffcdd2afd..fd53d87878 100644 --- a/assets/js/1be78505.9bd92206.js +++ b/assets/js/1be78505.f41f285f.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8714,1774],{50010:(e,t,n)=>{n.r(t),n.d(t,{default:()=>ge});var a=n(96540),l=n(20053),o=n(69024),r=n(17559),c=n(2967),i=n(84142),s=n(32252),d=n(26588),m=n(78511),u=n(21312),b=n(23104),p=n(75062);const h={backToTopButton:"backToTopButton_sjWU",backToTopButtonShow:"backToTopButtonShow_xfvO"};function E(){const{shown:e,scrollToTop:t}=function(e){let{threshold:t}=e;const[n,l]=(0,a.useState)(!1),o=(0,a.useRef)(!1),{startScroll:r,cancelScroll:c}=(0,b.gk)();return(0,b.Mq)(((e,n)=>{let{scrollY:a}=e;const r=n?.scrollY;r&&(o.current?o.current=!1:a>=r?(c(),l(!1)):a{e.location.hash&&(o.current=!0,l(!1))})),{shown:n,scrollToTop:()=>r(0)}}({threshold:300});return a.createElement("button",{"aria-label":(0,u.T)({id:"theme.BackToTopButton.buttonAriaLabel",message:"Scroll back to top",description:"The ARIA label for the back to top button"}),className:(0,l.A)("clean-btn",r.G.common.backToTopButton,h.backToTopButton,e&&h.backToTopButtonShow),type:"button",onClick:t})}var f=n(53109),g=n(56347),v=n(24581),_=n(6342),A=n(23465),C=n(58168);function k(e){return a.createElement("svg",(0,C.A)({width:"20",height:"20","aria-hidden":"true"},e),a.createElement("g",{fill:"#7a7a7a"},a.createElement("path",{d:"M9.992 10.023c0 .2-.062.399-.172.547l-4.996 7.492a.982.982 0 01-.828.454H1c-.55 0-1-.453-1-1 0-.2.059-.403.168-.551l4.629-6.942L.168 3.078A.939.939 0 010 2.528c0-.548.45-.997 1-.997h2.996c.352 0 .649.18.828.45L9.82 9.472c.11.148.172.347.172.55zm0 0"}),a.createElement("path",{d:"M19.98 10.023c0 .2-.058.399-.168.547l-4.996 7.492a.987.987 0 01-.828.454h-3c-.547 0-.996-.453-.996-1 0-.2.059-.403.168-.551l4.625-6.942-4.625-6.945a.939.939 0 01-.168-.55 1 1 0 01.996-.997h3c.348 0 .649.18.828.45l4.996 7.492c.11.148.168.347.168.55zm0 0"})))}const S={collapseSidebarButton:"collapseSidebarButton_PEFL",collapseSidebarButtonIcon:"collapseSidebarButtonIcon_kv0_"};function N(e){let{onClick:t}=e;return a.createElement("button",{type:"button",title:(0,u.T)({id:"theme.docs.sidebar.collapseButtonTitle",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),"aria-label":(0,u.T)({id:"theme.docs.sidebar.collapseButtonAriaLabel",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),className:(0,l.A)("button button--secondary button--outline",S.collapseSidebarButton),onClick:t},a.createElement(k,{className:S.collapseSidebarButtonIcon}))}var T=n(65041),I=n(89532);const x=Symbol("EmptyContext"),B=a.createContext(x);function w(e){let{children:t}=e;const[n,l]=(0,a.useState)(null),o=(0,a.useMemo)((()=>({expandedItem:n,setExpandedItem:l})),[n]);return a.createElement(B.Provider,{value:o},t)}var y=n(41422),L=n(99169),M=n(75489),H=n(92303);function P(e){let{categoryLabel:t,onClick:n}=e;return a.createElement("button",{"aria-label":(0,u.T)({id:"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel",message:"Toggle the collapsible sidebar category '{label}'",description:"The ARIA label to toggle the collapsible sidebar category"},{label:t}),type:"button",className:"clean-btn menu__caret",onClick:n})}function G(e){let{item:t,onItemClick:n,activePath:o,level:c,index:s,...d}=e;const{items:m,label:u,collapsible:b,className:p,href:h}=t,{docs:{sidebar:{autoCollapseCategories:E}}}=(0,_.p)(),f=function(e){const t=(0,H.A)();return(0,a.useMemo)((()=>e.href?e.href:!t&&e.collapsible?(0,i._o)(e):void 0),[e,t])}(t),g=(0,i.w8)(t,o),v=(0,L.ys)(h,o),{collapsed:A,setCollapsed:k}=(0,y.u)({initialState:()=>!!b&&(!g&&t.collapsed)}),{expandedItem:S,setExpandedItem:N}=function(){const e=(0,a.useContext)(B);if(e===x)throw new I.dV("DocSidebarItemsExpandedStateProvider");return e}(),T=function(e){void 0===e&&(e=!A),N(e?null:s),k(e)};return function(e){let{isActive:t,collapsed:n,updateCollapsed:l}=e;const o=(0,I.ZC)(t);(0,a.useEffect)((()=>{t&&!o&&n&&l(!1)}),[t,o,n,l])}({isActive:g,collapsed:A,updateCollapsed:T}),(0,a.useEffect)((()=>{b&&null!=S&&S!==s&&E&&k(!0)}),[b,S,s,k,E]),a.createElement("li",{className:(0,l.A)(r.G.docs.docSidebarItemCategory,r.G.docs.docSidebarItemCategoryLevel(c),"menu__list-item",{"menu__list-item--collapsed":A},p)},a.createElement("div",{className:(0,l.A)("menu__list-item-collapsible",{"menu__list-item-collapsible--active":v})},a.createElement(M.A,(0,C.A)({className:(0,l.A)("menu__link",{"menu__link--sublist":b,"menu__link--sublist-caret":!h&&b,"menu__link--active":g}),onClick:b?e=>{n?.(t),h?T(!1):(e.preventDefault(),T())}:()=>{n?.(t)},"aria-current":v?"page":void 0,"aria-expanded":b?!A:void 0,href:b?f??"#":f},d),u),h&&b&&a.createElement(P,{categoryLabel:u,onClick:e=>{e.preventDefault(),T()}})),a.createElement(y.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:A},a.createElement(K,{items:m,tabIndex:A?-1:0,onItemClick:n,activePath:o,level:c+1})))}var F=n(16654),W=n(43186);const D={menuExternalLink:"menuExternalLink_NmtK"};function V(e){let{item:t,onItemClick:n,activePath:o,level:c,index:s,...d}=e;const{href:m,label:u,className:b,autoAddBaseUrl:p}=t,h=(0,i.w8)(t,o),E=(0,F.A)(m);return a.createElement("li",{className:(0,l.A)(r.G.docs.docSidebarItemLink,r.G.docs.docSidebarItemLinkLevel(c),"menu__list-item",b),key:u},a.createElement(M.A,(0,C.A)({className:(0,l.A)("menu__link",!E&&D.menuExternalLink,{"menu__link--active":h}),autoAddBaseUrl:p,"aria-current":h?"page":void 0,to:m},E&&{onClick:n?()=>n(t):void 0},d),u,!E&&a.createElement(W.A,null)))}const U={menuHtmlItem:"menuHtmlItem_M9Kj"};function z(e){let{item:t,level:n,index:o}=e;const{value:c,defaultStyle:i,className:s}=t;return a.createElement("li",{className:(0,l.A)(r.G.docs.docSidebarItemLink,r.G.docs.docSidebarItemLinkLevel(n),i&&[U.menuHtmlItem,"menu__list-item"],s),key:o,dangerouslySetInnerHTML:{__html:c}})}function R(e){let{item:t,...n}=e;switch(t.type){case"category":return a.createElement(G,(0,C.A)({item:t},n));case"html":return a.createElement(z,(0,C.A)({item:t},n));default:return a.createElement(V,(0,C.A)({item:t},n))}}function j(e){let{items:t,...n}=e;return a.createElement(w,null,t.map(((e,t)=>a.createElement(R,(0,C.A)({key:t,item:e,index:t},n)))))}const K=(0,a.memo)(j),q={menu:"menu_SIkG",menuWithAnnouncementBar:"menuWithAnnouncementBar_GW3s"};function O(e){let{path:t,sidebar:n,className:o}=e;const c=function(){const{isActive:e}=(0,T.Mj)(),[t,n]=(0,a.useState)(e);return(0,b.Mq)((t=>{let{scrollY:a}=t;e&&n(0===a)}),[e]),e&&t}();return a.createElement("nav",{"aria-label":(0,u.T)({id:"theme.docs.sidebar.navAriaLabel",message:"Docs sidebar",description:"The ARIA label for the sidebar navigation"}),className:(0,l.A)("menu thin-scrollbar",q.menu,c&&q.menuWithAnnouncementBar,o)},a.createElement("ul",{className:(0,l.A)(r.G.docs.docSidebarMenu,"menu__list")},a.createElement(K,{items:n,activePath:t,level:1})))}const X="sidebar_njMd",Y="sidebarWithHideableNavbar_wUlq",Z="sidebarHidden_VK0M",$="sidebarLogo_isFc";function J(e){let{path:t,sidebar:n,onCollapse:o,isHidden:r}=e;const{navbar:{hideOnScroll:c},docs:{sidebar:{hideable:i}}}=(0,_.p)();return a.createElement("div",{className:(0,l.A)(X,c&&Y,r&&Z)},c&&a.createElement(A.A,{tabIndex:-1,className:$}),a.createElement(O,{path:t,sidebar:n}),i&&a.createElement(N,{onClick:o}))}const Q=a.memo(J);var ee=n(75600),te=n(22069);const ne=e=>{let{sidebar:t,path:n}=e;const o=(0,te.M)();return a.createElement("ul",{className:(0,l.A)(r.G.docs.docSidebarMenu,"menu__list")},a.createElement(K,{items:t,activePath:n,onItemClick:e=>{"category"===e.type&&e.href&&o.toggle(),"link"===e.type&&o.toggle()},level:1}))};function ae(e){return a.createElement(ee.GX,{component:ne,props:e})}const le=a.memo(ae);function oe(e){const t=(0,v.l)(),n="desktop"===t||"ssr"===t,l="mobile"===t;return a.createElement(a.Fragment,null,n&&a.createElement(Q,e),l&&a.createElement(le,e))}const re={expandButton:"expandButton_m80_",expandButtonIcon:"expandButtonIcon_BlDH"};function ce(e){let{toggleSidebar:t}=e;return a.createElement("div",{className:re.expandButton,title:(0,u.T)({id:"theme.docs.sidebar.expandButtonTitle",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),"aria-label":(0,u.T)({id:"theme.docs.sidebar.expandButtonAriaLabel",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),tabIndex:0,role:"button",onKeyDown:t,onClick:t},a.createElement(k,{className:re.expandButtonIcon}))}const ie={docSidebarContainer:"docSidebarContainer_b6E3",docSidebarContainerHidden:"docSidebarContainerHidden_b3ry",sidebarViewport:"sidebarViewport_Xe31"};function se(e){let{children:t}=e;const n=(0,d.t)();return a.createElement(a.Fragment,{key:n?.name??"noSidebar"},t)}function de(e){let{sidebar:t,hiddenSidebarContainer:n,setHiddenSidebarContainer:o}=e;const{pathname:c}=(0,g.zy)(),[i,s]=(0,a.useState)(!1),d=(0,a.useCallback)((()=>{i&&s(!1),!i&&(0,f.O)()&&s(!0),o((e=>!e))}),[o,i]);return a.createElement("aside",{className:(0,l.A)(r.G.docs.docSidebarContainer,ie.docSidebarContainer,n&&ie.docSidebarContainerHidden),onTransitionEnd:e=>{e.currentTarget.classList.contains(ie.docSidebarContainer)&&n&&s(!0)}},a.createElement(se,null,a.createElement("div",{className:(0,l.A)(ie.sidebarViewport,i&&ie.sidebarViewportHidden)},a.createElement(oe,{sidebar:t,path:c,onCollapse:d,isHidden:i}),i&&a.createElement(ce,{toggleSidebar:d}))))}const me={docMainContainer:"docMainContainer_gTbr",docMainContainerEnhanced:"docMainContainerEnhanced_Uz_u",docItemWrapperEnhanced:"docItemWrapperEnhanced_czyv"};function ue(e){let{hiddenSidebarContainer:t,children:n}=e;const o=(0,d.t)();return a.createElement("main",{className:(0,l.A)(me.docMainContainer,(t||!o)&&me.docMainContainerEnhanced)},a.createElement("div",{className:(0,l.A)("container padding-top--md padding-bottom--lg",me.docItemWrapper,t&&me.docItemWrapperEnhanced)},n))}const be={docPage:"docPage__5DB",docsWrapper:"docsWrapper_BCFX","themedComponent--light":"themedComponent--light_NU7w"};function pe(e){let{children:t}=e;const n=(0,d.t)(),[l,o]=(0,a.useState)(!1);return a.createElement(m.A,{wrapperClassName:be.docsWrapper},a.createElement(E,null),a.createElement("div",{className:be.docPage},n&&a.createElement(de,{sidebar:n.items,hiddenSidebarContainer:l,setHiddenSidebarContainer:o}),a.createElement(ue,{hiddenSidebarContainer:l},t)))}var he=n(81774),Ee=n(41463);function fe(e){const{versionMetadata:t}=e;return a.createElement(a.Fragment,null,a.createElement(Ee.A,{version:t.version,tag:(0,c.tU)(t.pluginId,t.version)}),a.createElement(o.be,null,t.noIndex&&a.createElement("meta",{name:"robots",content:"noindex, nofollow"})))}function ge(e){const{versionMetadata:t}=e,n=(0,i.mz)(e);if(!n)return a.createElement(he.default,null);const{docElement:c,sidebarName:m,sidebarItems:u}=n;return a.createElement(a.Fragment,null,a.createElement(fe,e),a.createElement(o.e3,{className:(0,l.A)(r.G.wrapper.docsPages,r.G.page.docsDocPage,e.versionMetadata.className)},a.createElement(s.n,{version:t},a.createElement(d.V,{name:m,items:u},a.createElement(pe,null,c)))))}},81774:(e,t,n)=>{n.r(t),n.d(t,{default:()=>c});var a=n(96540),l=n(21312),o=n(69024),r=n(78511);function c(){return a.createElement(a.Fragment,null,a.createElement(o.be,{title:(0,l.T)({id:"theme.NotFound.title",message:"Page Not Found"})}),a.createElement(r.A,null,a.createElement("main",{className:"container margin-vert--xl"},a.createElement("div",{className:"row"},a.createElement("div",{className:"col col--6 col--offset-3"},a.createElement("h1",{className:"hero__title"},a.createElement(l.A,{id:"theme.NotFound.title",description:"The title of the 404 page"},"Page Not Found")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page"},"We could not find what you were looking for.")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page"},"Please contact the owner of the site that linked you to the original URL and let them know their link is broken.")))))))}},32252:(e,t,n)=>{n.d(t,{n:()=>r,r:()=>c});var a=n(96540),l=n(89532);const o=a.createContext(null);function r(e){let{children:t,version:n}=e;return a.createElement(o.Provider,{value:n},t)}function c(){const e=(0,a.useContext)(o);if(null===e)throw new l.dV("DocsVersionProvider");return e}}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8714,1774],{50010:(e,t,n)=>{n.r(t),n.d(t,{default:()=>ge});var a=n(96540),l=n(20053),o=n(69024),r=n(17559),c=n(2967),i=n(84142),s=n(32252),d=n(26588),m=n(98956),u=n(21312),b=n(23104),p=n(75062);const h={backToTopButton:"backToTopButton_sjWU",backToTopButtonShow:"backToTopButtonShow_xfvO"};function E(){const{shown:e,scrollToTop:t}=function(e){let{threshold:t}=e;const[n,l]=(0,a.useState)(!1),o=(0,a.useRef)(!1),{startScroll:r,cancelScroll:c}=(0,b.gk)();return(0,b.Mq)(((e,n)=>{let{scrollY:a}=e;const r=n?.scrollY;r&&(o.current?o.current=!1:a>=r?(c(),l(!1)):a{e.location.hash&&(o.current=!0,l(!1))})),{shown:n,scrollToTop:()=>r(0)}}({threshold:300});return a.createElement("button",{"aria-label":(0,u.T)({id:"theme.BackToTopButton.buttonAriaLabel",message:"Scroll back to top",description:"The ARIA label for the back to top button"}),className:(0,l.A)("clean-btn",r.G.common.backToTopButton,h.backToTopButton,e&&h.backToTopButtonShow),type:"button",onClick:t})}var f=n(53109),g=n(56347),v=n(24581),_=n(6342),A=n(23465),C=n(58168);function k(e){return a.createElement("svg",(0,C.A)({width:"20",height:"20","aria-hidden":"true"},e),a.createElement("g",{fill:"#7a7a7a"},a.createElement("path",{d:"M9.992 10.023c0 .2-.062.399-.172.547l-4.996 7.492a.982.982 0 01-.828.454H1c-.55 0-1-.453-1-1 0-.2.059-.403.168-.551l4.629-6.942L.168 3.078A.939.939 0 010 2.528c0-.548.45-.997 1-.997h2.996c.352 0 .649.18.828.45L9.82 9.472c.11.148.172.347.172.55zm0 0"}),a.createElement("path",{d:"M19.98 10.023c0 .2-.058.399-.168.547l-4.996 7.492a.987.987 0 01-.828.454h-3c-.547 0-.996-.453-.996-1 0-.2.059-.403.168-.551l4.625-6.942-4.625-6.945a.939.939 0 01-.168-.55 1 1 0 01.996-.997h3c.348 0 .649.18.828.45l4.996 7.492c.11.148.168.347.168.55zm0 0"})))}const S={collapseSidebarButton:"collapseSidebarButton_PEFL",collapseSidebarButtonIcon:"collapseSidebarButtonIcon_kv0_"};function N(e){let{onClick:t}=e;return a.createElement("button",{type:"button",title:(0,u.T)({id:"theme.docs.sidebar.collapseButtonTitle",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),"aria-label":(0,u.T)({id:"theme.docs.sidebar.collapseButtonAriaLabel",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),className:(0,l.A)("button button--secondary button--outline",S.collapseSidebarButton),onClick:t},a.createElement(k,{className:S.collapseSidebarButtonIcon}))}var T=n(65041),I=n(89532);const x=Symbol("EmptyContext"),B=a.createContext(x);function w(e){let{children:t}=e;const[n,l]=(0,a.useState)(null),o=(0,a.useMemo)((()=>({expandedItem:n,setExpandedItem:l})),[n]);return a.createElement(B.Provider,{value:o},t)}var y=n(41422),L=n(99169),M=n(75489),H=n(92303);function P(e){let{categoryLabel:t,onClick:n}=e;return a.createElement("button",{"aria-label":(0,u.T)({id:"theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel",message:"Toggle the collapsible sidebar category '{label}'",description:"The ARIA label to toggle the collapsible sidebar category"},{label:t}),type:"button",className:"clean-btn menu__caret",onClick:n})}function G(e){let{item:t,onItemClick:n,activePath:o,level:c,index:s,...d}=e;const{items:m,label:u,collapsible:b,className:p,href:h}=t,{docs:{sidebar:{autoCollapseCategories:E}}}=(0,_.p)(),f=function(e){const t=(0,H.A)();return(0,a.useMemo)((()=>e.href?e.href:!t&&e.collapsible?(0,i._o)(e):void 0),[e,t])}(t),g=(0,i.w8)(t,o),v=(0,L.ys)(h,o),{collapsed:A,setCollapsed:k}=(0,y.u)({initialState:()=>!!b&&(!g&&t.collapsed)}),{expandedItem:S,setExpandedItem:N}=function(){const e=(0,a.useContext)(B);if(e===x)throw new I.dV("DocSidebarItemsExpandedStateProvider");return e}(),T=function(e){void 0===e&&(e=!A),N(e?null:s),k(e)};return function(e){let{isActive:t,collapsed:n,updateCollapsed:l}=e;const o=(0,I.ZC)(t);(0,a.useEffect)((()=>{t&&!o&&n&&l(!1)}),[t,o,n,l])}({isActive:g,collapsed:A,updateCollapsed:T}),(0,a.useEffect)((()=>{b&&null!=S&&S!==s&&E&&k(!0)}),[b,S,s,k,E]),a.createElement("li",{className:(0,l.A)(r.G.docs.docSidebarItemCategory,r.G.docs.docSidebarItemCategoryLevel(c),"menu__list-item",{"menu__list-item--collapsed":A},p)},a.createElement("div",{className:(0,l.A)("menu__list-item-collapsible",{"menu__list-item-collapsible--active":v})},a.createElement(M.A,(0,C.A)({className:(0,l.A)("menu__link",{"menu__link--sublist":b,"menu__link--sublist-caret":!h&&b,"menu__link--active":g}),onClick:b?e=>{n?.(t),h?T(!1):(e.preventDefault(),T())}:()=>{n?.(t)},"aria-current":v?"page":void 0,"aria-expanded":b?!A:void 0,href:b?f??"#":f},d),u),h&&b&&a.createElement(P,{categoryLabel:u,onClick:e=>{e.preventDefault(),T()}})),a.createElement(y.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:A},a.createElement(K,{items:m,tabIndex:A?-1:0,onItemClick:n,activePath:o,level:c+1})))}var F=n(16654),W=n(43186);const D={menuExternalLink:"menuExternalLink_NmtK"};function V(e){let{item:t,onItemClick:n,activePath:o,level:c,index:s,...d}=e;const{href:m,label:u,className:b,autoAddBaseUrl:p}=t,h=(0,i.w8)(t,o),E=(0,F.A)(m);return a.createElement("li",{className:(0,l.A)(r.G.docs.docSidebarItemLink,r.G.docs.docSidebarItemLinkLevel(c),"menu__list-item",b),key:u},a.createElement(M.A,(0,C.A)({className:(0,l.A)("menu__link",!E&&D.menuExternalLink,{"menu__link--active":h}),autoAddBaseUrl:p,"aria-current":h?"page":void 0,to:m},E&&{onClick:n?()=>n(t):void 0},d),u,!E&&a.createElement(W.A,null)))}const U={menuHtmlItem:"menuHtmlItem_M9Kj"};function z(e){let{item:t,level:n,index:o}=e;const{value:c,defaultStyle:i,className:s}=t;return a.createElement("li",{className:(0,l.A)(r.G.docs.docSidebarItemLink,r.G.docs.docSidebarItemLinkLevel(n),i&&[U.menuHtmlItem,"menu__list-item"],s),key:o,dangerouslySetInnerHTML:{__html:c}})}function R(e){let{item:t,...n}=e;switch(t.type){case"category":return a.createElement(G,(0,C.A)({item:t},n));case"html":return a.createElement(z,(0,C.A)({item:t},n));default:return a.createElement(V,(0,C.A)({item:t},n))}}function j(e){let{items:t,...n}=e;return a.createElement(w,null,t.map(((e,t)=>a.createElement(R,(0,C.A)({key:t,item:e,index:t},n)))))}const K=(0,a.memo)(j),q={menu:"menu_SIkG",menuWithAnnouncementBar:"menuWithAnnouncementBar_GW3s"};function O(e){let{path:t,sidebar:n,className:o}=e;const c=function(){const{isActive:e}=(0,T.Mj)(),[t,n]=(0,a.useState)(e);return(0,b.Mq)((t=>{let{scrollY:a}=t;e&&n(0===a)}),[e]),e&&t}();return a.createElement("nav",{"aria-label":(0,u.T)({id:"theme.docs.sidebar.navAriaLabel",message:"Docs sidebar",description:"The ARIA label for the sidebar navigation"}),className:(0,l.A)("menu thin-scrollbar",q.menu,c&&q.menuWithAnnouncementBar,o)},a.createElement("ul",{className:(0,l.A)(r.G.docs.docSidebarMenu,"menu__list")},a.createElement(K,{items:n,activePath:t,level:1})))}const X="sidebar_njMd",Y="sidebarWithHideableNavbar_wUlq",Z="sidebarHidden_VK0M",$="sidebarLogo_isFc";function J(e){let{path:t,sidebar:n,onCollapse:o,isHidden:r}=e;const{navbar:{hideOnScroll:c},docs:{sidebar:{hideable:i}}}=(0,_.p)();return a.createElement("div",{className:(0,l.A)(X,c&&Y,r&&Z)},c&&a.createElement(A.A,{tabIndex:-1,className:$}),a.createElement(O,{path:t,sidebar:n}),i&&a.createElement(N,{onClick:o}))}const Q=a.memo(J);var ee=n(75600),te=n(22069);const ne=e=>{let{sidebar:t,path:n}=e;const o=(0,te.M)();return a.createElement("ul",{className:(0,l.A)(r.G.docs.docSidebarMenu,"menu__list")},a.createElement(K,{items:t,activePath:n,onItemClick:e=>{"category"===e.type&&e.href&&o.toggle(),"link"===e.type&&o.toggle()},level:1}))};function ae(e){return a.createElement(ee.GX,{component:ne,props:e})}const le=a.memo(ae);function oe(e){const t=(0,v.l)(),n="desktop"===t||"ssr"===t,l="mobile"===t;return a.createElement(a.Fragment,null,n&&a.createElement(Q,e),l&&a.createElement(le,e))}const re={expandButton:"expandButton_m80_",expandButtonIcon:"expandButtonIcon_BlDH"};function ce(e){let{toggleSidebar:t}=e;return a.createElement("div",{className:re.expandButton,title:(0,u.T)({id:"theme.docs.sidebar.expandButtonTitle",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),"aria-label":(0,u.T)({id:"theme.docs.sidebar.expandButtonAriaLabel",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),tabIndex:0,role:"button",onKeyDown:t,onClick:t},a.createElement(k,{className:re.expandButtonIcon}))}const ie={docSidebarContainer:"docSidebarContainer_b6E3",docSidebarContainerHidden:"docSidebarContainerHidden_b3ry",sidebarViewport:"sidebarViewport_Xe31"};function se(e){let{children:t}=e;const n=(0,d.t)();return a.createElement(a.Fragment,{key:n?.name??"noSidebar"},t)}function de(e){let{sidebar:t,hiddenSidebarContainer:n,setHiddenSidebarContainer:o}=e;const{pathname:c}=(0,g.zy)(),[i,s]=(0,a.useState)(!1),d=(0,a.useCallback)((()=>{i&&s(!1),!i&&(0,f.O)()&&s(!0),o((e=>!e))}),[o,i]);return a.createElement("aside",{className:(0,l.A)(r.G.docs.docSidebarContainer,ie.docSidebarContainer,n&&ie.docSidebarContainerHidden),onTransitionEnd:e=>{e.currentTarget.classList.contains(ie.docSidebarContainer)&&n&&s(!0)}},a.createElement(se,null,a.createElement("div",{className:(0,l.A)(ie.sidebarViewport,i&&ie.sidebarViewportHidden)},a.createElement(oe,{sidebar:t,path:c,onCollapse:d,isHidden:i}),i&&a.createElement(ce,{toggleSidebar:d}))))}const me={docMainContainer:"docMainContainer_gTbr",docMainContainerEnhanced:"docMainContainerEnhanced_Uz_u",docItemWrapperEnhanced:"docItemWrapperEnhanced_czyv"};function ue(e){let{hiddenSidebarContainer:t,children:n}=e;const o=(0,d.t)();return a.createElement("main",{className:(0,l.A)(me.docMainContainer,(t||!o)&&me.docMainContainerEnhanced)},a.createElement("div",{className:(0,l.A)("container padding-top--md padding-bottom--lg",me.docItemWrapper,t&&me.docItemWrapperEnhanced)},n))}const be={docPage:"docPage__5DB",docsWrapper:"docsWrapper_BCFX","themedComponent--light":"themedComponent--light_NU7w"};function pe(e){let{children:t}=e;const n=(0,d.t)(),[l,o]=(0,a.useState)(!1);return a.createElement(m.A,{wrapperClassName:be.docsWrapper},a.createElement(E,null),a.createElement("div",{className:be.docPage},n&&a.createElement(de,{sidebar:n.items,hiddenSidebarContainer:l,setHiddenSidebarContainer:o}),a.createElement(ue,{hiddenSidebarContainer:l},t)))}var he=n(81774),Ee=n(41463);function fe(e){const{versionMetadata:t}=e;return a.createElement(a.Fragment,null,a.createElement(Ee.A,{version:t.version,tag:(0,c.tU)(t.pluginId,t.version)}),a.createElement(o.be,null,t.noIndex&&a.createElement("meta",{name:"robots",content:"noindex, nofollow"})))}function ge(e){const{versionMetadata:t}=e,n=(0,i.mz)(e);if(!n)return a.createElement(he.default,null);const{docElement:c,sidebarName:m,sidebarItems:u}=n;return a.createElement(a.Fragment,null,a.createElement(fe,e),a.createElement(o.e3,{className:(0,l.A)(r.G.wrapper.docsPages,r.G.page.docsDocPage,e.versionMetadata.className)},a.createElement(s.n,{version:t},a.createElement(d.V,{name:m,items:u},a.createElement(pe,null,c)))))}},81774:(e,t,n)=>{n.r(t),n.d(t,{default:()=>c});var a=n(96540),l=n(21312),o=n(69024),r=n(98956);function c(){return a.createElement(a.Fragment,null,a.createElement(o.be,{title:(0,l.T)({id:"theme.NotFound.title",message:"Page Not Found"})}),a.createElement(r.A,null,a.createElement("main",{className:"container margin-vert--xl"},a.createElement("div",{className:"row"},a.createElement("div",{className:"col col--6 col--offset-3"},a.createElement("h1",{className:"hero__title"},a.createElement(l.A,{id:"theme.NotFound.title",description:"The title of the 404 page"},"Page Not Found")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page"},"We could not find what you were looking for.")),a.createElement("p",null,a.createElement(l.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page"},"Please contact the owner of the site that linked you to the original URL and let them know their link is broken.")))))))}},32252:(e,t,n)=>{n.d(t,{n:()=>r,r:()=>c});var a=n(96540),l=n(89532);const o=a.createContext(null);function r(e){let{children:t,version:n}=e;return a.createElement(o.Provider,{value:n},t)}function c(){const e=(0,a.useContext)(o);if(null===e)throw new l.dV("DocsVersionProvider");return e}}}]); \ No newline at end of file diff --git a/assets/js/1ca907c0.8af28e47.js b/assets/js/1ca907c0.475dd85b.js similarity index 96% rename from assets/js/1ca907c0.8af28e47.js rename to assets/js/1ca907c0.475dd85b.js index 60e7d60de3..a38ef83b0a 100644 --- a/assets/js/1ca907c0.8af28e47.js +++ b/assets/js/1ca907c0.475dd85b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1431],{85483:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),r=(i(96540),i(15680));i(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-7.0.0/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories.",source:"@site/versioned_docs/version-7.0.0/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/implementing-security.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},sidebar:"docs",previous:{title:"Fine grained security",permalink:"/docs/fine-grained-security"},next:{title:"Operation complexity",permalink:"/docs/operation-complexity"}},c={},l=[],u={toc:l},p="wrapper";function h(e){let{components:t,...i}=e;return(0,r.yg)(p,(0,n.A)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories."),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1431],{85483:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),r=(i(96540),i(15680));i(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-7.0.0/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories.",source:"@site/versioned_docs/version-7.0.0/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/implementing-security.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},sidebar:"docs",previous:{title:"Fine grained security",permalink:"/docs/fine-grained-security"},next:{title:"Operation complexity",permalink:"/docs/operation-complexity"}},c={},l=[],u={toc:l},p="wrapper";function h(e){let{components:t,...i}=e;return(0,r.yg)(p,(0,n.A)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories."),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1d20a4b3.a82a7192.js b/assets/js/1d20a4b3.3ae04557.js similarity index 99% rename from assets/js/1d20a4b3.a82a7192.js rename to assets/js/1d20a4b3.3ae04557.js index 8b95a8d8e1..606e435459 100644 --- a/assets/js/1d20a4b3.a82a7192.js +++ b/assets/js/1d20a4b3.3ae04557.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2596],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),l=t(20053),o=t(23104),i=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,i.W6)(),l=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(n.location.search);a.set(l,e),n.replace({...n.location,search:a.toString()})}),[l,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,l=g(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:l}))),[s,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,l]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:n}),m=(()=>{const e=s??c;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==i&&(c(a),s(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===a?0:-1,"aria-selected":i===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},53945:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),o=t(19365);const i={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},s=void 0,u={unversionedId:"fine-grained-security",id:"version-7.0.0/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-7.0.0/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/fine-grained-security.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},sidebar:"docs",previous:{title:"Authentication and authorization",permalink:"/docs/authentication-authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/implementing-security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/authentication-authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/authentication-authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2596],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),l=t(20053),o=t(23104),i=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,i.W6)(),l=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(n.location.search);a.set(l,e),n.replace({...n.location,search:a.toString()})}),[l,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,l=g(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:l}))),[s,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,l]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:n}),m=(()=>{const e=s??c;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==i&&(c(a),s(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===a?0:-1,"aria-selected":i===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},53945:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),o=t(19365);const i={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},s=void 0,u={unversionedId:"fine-grained-security",id:"version-7.0.0/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-7.0.0/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/fine-grained-security.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},sidebar:"docs",previous:{title:"Authentication and authorization",permalink:"/docs/authentication-authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/implementing-security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/authentication-authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/authentication-authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1d703573.f3aa0b11.js b/assets/js/1d703573.c56e80a3.js similarity index 83% rename from assets/js/1d703573.f3aa0b11.js rename to assets/js/1d703573.c56e80a3.js index 9f12be46be..e88ea37c8a 100644 --- a/assets/js/1d703573.f3aa0b11.js +++ b/assets/js/1d703573.c56e80a3.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4671],{62190:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>c,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},o=void 0,s={unversionedId:"getting-started",id:"version-4.2/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-4.2/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/4.2/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/getting-started.md",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},sidebar:"version-4.2/docs",previous:{title:"GraphQLite",permalink:"/docs/4.2/"},next:{title:"Symfony bundle",permalink:"/docs/4.2/symfony-bundle"}},d={},l=[],p={toc:l},g="wrapper";function c(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,r.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/universal-service-providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/other-frameworks"},"Get started with another framework (or no framework)"))))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4671],{62190:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>p,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},o=void 0,s={unversionedId:"getting-started",id:"version-4.2/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-4.2/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/4.2/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/getting-started.md",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},sidebar:"version-4.2/docs",previous:{title:"GraphQLite",permalink:"/docs/4.2/"},next:{title:"Symfony bundle",permalink:"/docs/4.2/symfony-bundle"}},d={},l=[],c={toc:l},g="wrapper";function p(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,r.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/universal-service-providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/other-frameworks"},"Get started with another framework (or no framework)"))))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1e2c5f46.3a7a0bf9.js b/assets/js/1e2c5f46.2622c239.js similarity index 97% rename from assets/js/1e2c5f46.3a7a0bf9.js rename to assets/js/1e2c5f46.2622c239.js index f7090532ff..eb44287f20 100644 --- a/assets/js/1e2c5f46.3a7a0bf9.js +++ b/assets/js/1e2c5f46.2622c239.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2491],{30241:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-6.1/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/versioned_docs/version-6.1/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/6.1/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/semver.md",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"docs",previous:{title:"Annotations reference",permalink:"/docs/6.1/annotations-reference"},next:{title:"Changelog",permalink:"/docs/6.1/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^6"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~6.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2491],{30241:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-6.1/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/versioned_docs/version-6.1/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/6.1/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/semver.md",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"docs",previous:{title:"Annotations reference",permalink:"/docs/6.1/annotations-reference"},next:{title:"Changelog",permalink:"/docs/6.1/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^6"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~6.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1e6ec01e.8ee3df72.js b/assets/js/1e6ec01e.b01c0803.js similarity index 98% rename from assets/js/1e6ec01e.8ee3df72.js rename to assets/js/1e6ec01e.b01c0803.js index 8dea120691..7a96432082 100644 --- a/assets/js/1e6ec01e.8ee3df72.js +++ b/assets/js/1e6ec01e.b01c0803.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4761],{89714:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var n=t(58168),r=(t(96540),t(15680));t(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/docs/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/next/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/laravel-package.md",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"docs",previous:{title:"Symfony bundle",permalink:"/docs/next/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/next/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...t}=e;return(0,r.yg)(h,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},'$ php artisan vendor:publish --provider="TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider"\n')),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4761],{89714:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var n=t(58168),r=(t(96540),t(15680));t(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/docs/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/next/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/laravel-package.md",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"docs",previous:{title:"Symfony bundle",permalink:"/docs/next/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/next/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...t}=e;return(0,r.yg)(h,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},'$ php artisan vendor:publish --provider="TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider"\n')),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1e7fe27e.6683c9a6.js b/assets/js/1e7fe27e.1eb07132.js similarity index 72% rename from assets/js/1e7fe27e.6683c9a6.js rename to assets/js/1e7fe27e.1eb07132.js index 663e2d2b63..27dbae295d 100644 --- a/assets/js/1e7fe27e.6683c9a6.js +++ b/assets/js/1e7fe27e.1eb07132.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[79],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),r=n(96540),i=n(20053),o=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function p(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:n,groupId:a}),[d,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(d(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),n??t)})))}function b(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,a.A)({},e,t)),r.createElement(b,(0,a.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},71930:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var a=n(58168),r=(n(96540),n(15680)),i=(n(67443),n(11470)),o=n(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-5.0/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-5.0/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/5.0/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/autowiring.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"version-5.0/docs",previous:{title:"Type mapping",permalink:"/docs/5.0/type-mapping"},next:{title:"Extending a type",permalink:"/docs/5.0/extend-type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[79],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),i=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(n.location.search);t.set(i,e),n.replace({...n.location,search:t.toString()})}),[i,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,i]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:n}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,t)),r.createElement(b,(0,n.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},71930:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),i=(a(67443),a(11470)),o=a(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-5.0/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-5.0/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/5.0/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/autowiring.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"version-5.0/docs",previous:{title:"Type mapping",permalink:"/docs/5.0/type-mapping"},next:{title:"Extending a type",permalink:"/docs/5.0/extend-type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(h,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1ea13486.7fc84ccf.js b/assets/js/1ea13486.10865491.js similarity index 94% rename from assets/js/1ea13486.7fc84ccf.js rename to assets/js/1ea13486.10865491.js index 942222c6a6..b62ab71b7c 100644 --- a/assets/js/1ea13486.7fc84ccf.js +++ b/assets/js/1ea13486.10865491.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2112],{52660:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>n,metadata:()=>u,toc:()=>d});var i=r(58168),s=(r(96540),r(15680)),a=r(67443);const n={id:"automatic-persisted-queries",title:"Automatic persisted queries",sidebar_label:"Automatic persisted queries"},o=void 0,u={unversionedId:"automatic-persisted-queries",id:"automatic-persisted-queries",title:"Automatic persisted queries",description:"The problem",source:"@site/docs/automatic-persisted-queries.mdx",sourceDirName:".",slug:"/automatic-persisted-queries",permalink:"/docs/next/automatic-persisted-queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/automatic-persisted-queries.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"automatic-persisted-queries",title:"Automatic persisted queries",sidebar_label:"Automatic persisted queries"},sidebar:"docs",previous:{title:"Prefetching records",permalink:"/docs/next/prefetch-method"},next:{title:"File uploads",permalink:"/docs/next/file-uploads"}},l={},d=[{value:"The problem",id:"the-problem",level:2},{value:"Apollo APQ",id:"apollo-apq",level:2},{value:"Setup",id:"setup",level:2}],p={toc:d},c="wrapper";function h(e){let{components:t,...r}=e;return(0,s.yg)(c,(0,i.A)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,s.yg)("h2",{id:"the-problem"},"The problem"),(0,s.yg)("p",null,"Clients send queries to GraphQLite as HTTP requests that include the GraphQL string of the query to execute.\nDepending on your graph's schema, the size of a valid query string might be arbitrarily large.\nAs query strings become larger, increased latency and network usage can noticeably degrade client performance."),(0,s.yg)("p",null,'To combat this, GraphQL servers use a technique called "persisted queries". The basic idea is instead of\nsending the whole query string, clients only send it\'s unique identifier. The server then finds the actual\nquery string by given identifier and use that as if the client sent the whole query in the first place.\nThat helps improve GraphQL network performance with zero build-time configuration by sending smaller GraphQL HTTP requests.\nA smaller request payload reduces bandwidth utilization and speeds up GraphQL Client loading times.'),(0,s.yg)("h2",{id:"apollo-apq"},"Apollo APQ"),(0,s.yg)("p",null,(0,s.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/apollo-server/performance/apq/"},"Automatic persisted queries (APQ) is technique created by Apollo"),"\nand is aimed to implement a simple automatic way of persisting queries. Queries are cached on the server side,\nalong with its unique identifier (always its SHA-256 hash). Clients can send this identifier instead of the\ncorresponding query string, thus reducing request sizes dramatically (response sizes are unaffected)."),(0,s.yg)("p",null,"To persist a query string, GraphQLite server must first receive it from a requesting client.\nConsequently, each unique query string must be sent to Apollo Server at least once.\nAfter any client sends a query string to persist, every client that executes that query can then benefit from APQ."),(0,s.yg)(a.K,{chart:"sequenceDiagram;\n Client app->>GraphQL Server: Sends SHA-256 hash of query string to execute\n Note over GraphQL Server: Fails to find persisted query string\n GraphQL Server->>Client app: Responds with error\n Client app->>GraphQL Server: Sends both query string AND hash\n Note over GraphQL Server: Persists query string and hash\n GraphQL Server->>Client app: Executes query and returns result\n Note over Client app: Time passes\n Client app->>GraphQL Server: Sends SHA-256 hash of query string to execute\n Note over GraphQL Server: Finds persisted query string\n GraphQL Server->>Client app: Executes query and returns result",mdxType:"Mermaid"}),(0,s.yg)("p",null,"Persisted queries are especially effective when clients send queries as GET requests.\nThis enables clients to take advantage of the browser cache and integrate with a CDN."),(0,s.yg)("p",null,"Because query identifiers are deterministic hashes, clients can generate them at runtime. No additional build steps are required."),(0,s.yg)("h2",{id:"setup"},"Setup"),(0,s.yg)("p",null,"To use Automatic persisted queries with GraphQLite, you may use\n",(0,s.yg)("inlineCode",{parentName:"p"},"useAutomaticPersistedQueries")," method when building your PSR-15 middleware:"),(0,s.yg)("pre",null,(0,s.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n// You need to provide a PSR compatible cache and a TTL for queries. The best cache would be some kind\n// of in-memory cache with a limit on number of entries to make sure your cache can't be maliciously spammed with queries.\n$builder->useAutomaticPersistedQueries($cache, new DateInterval('PT1H'));\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2112],{52660:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>u,default:()=>h,frontMatter:()=>n,metadata:()=>o,toc:()=>d});var i=r(58168),s=(r(96540),r(15680)),a=r(67443);const n={id:"automatic-persisted-queries",title:"Automatic persisted queries",sidebar_label:"Automatic persisted queries"},u=void 0,o={unversionedId:"automatic-persisted-queries",id:"automatic-persisted-queries",title:"Automatic persisted queries",description:"The problem",source:"@site/docs/automatic-persisted-queries.mdx",sourceDirName:".",slug:"/automatic-persisted-queries",permalink:"/docs/next/automatic-persisted-queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/automatic-persisted-queries.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"automatic-persisted-queries",title:"Automatic persisted queries",sidebar_label:"Automatic persisted queries"},sidebar:"docs",previous:{title:"Prefetching records",permalink:"/docs/next/prefetch-method"},next:{title:"File uploads",permalink:"/docs/next/file-uploads"}},l={},d=[{value:"The problem",id:"the-problem",level:2},{value:"Apollo APQ",id:"apollo-apq",level:2},{value:"Setup",id:"setup",level:2}],p={toc:d},c="wrapper";function h(e){let{components:t,...r}=e;return(0,s.yg)(c,(0,i.A)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,s.yg)("h2",{id:"the-problem"},"The problem"),(0,s.yg)("p",null,"Clients send queries to GraphQLite as HTTP requests that include the GraphQL string of the query to execute.\nDepending on your graph's schema, the size of a valid query string might be arbitrarily large.\nAs query strings become larger, increased latency and network usage can noticeably degrade client performance."),(0,s.yg)("p",null,'To combat this, GraphQL servers use a technique called "persisted queries". The basic idea is instead of\nsending the whole query string, clients only send it\'s unique identifier. The server then finds the actual\nquery string by given identifier and use that as if the client sent the whole query in the first place.\nThat helps improve GraphQL network performance with zero build-time configuration by sending smaller GraphQL HTTP requests.\nA smaller request payload reduces bandwidth utilization and speeds up GraphQL Client loading times.'),(0,s.yg)("h2",{id:"apollo-apq"},"Apollo APQ"),(0,s.yg)("p",null,(0,s.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/apollo-server/performance/apq/"},"Automatic persisted queries (APQ) is technique created by Apollo"),"\nand is aimed to implement a simple automatic way of persisting queries. Queries are cached on the server side,\nalong with its unique identifier (always its SHA-256 hash). Clients can send this identifier instead of the\ncorresponding query string, thus reducing request sizes dramatically (response sizes are unaffected)."),(0,s.yg)("p",null,"To persist a query string, GraphQLite server must first receive it from a requesting client.\nConsequently, each unique query string must be sent to Apollo Server at least once.\nAfter any client sends a query string to persist, every client that executes that query can then benefit from APQ."),(0,s.yg)(a.K,{chart:"sequenceDiagram;\n Client app->>GraphQL Server: Sends SHA-256 hash of query string to execute\n Note over GraphQL Server: Fails to find persisted query string\n GraphQL Server->>Client app: Responds with error\n Client app->>GraphQL Server: Sends both query string AND hash\n Note over GraphQL Server: Persists query string and hash\n GraphQL Server->>Client app: Executes query and returns result\n Note over Client app: Time passes\n Client app->>GraphQL Server: Sends SHA-256 hash of query string to execute\n Note over GraphQL Server: Finds persisted query string\n GraphQL Server->>Client app: Executes query and returns result",mdxType:"Mermaid"}),(0,s.yg)("p",null,"Persisted queries are especially effective when clients send queries as GET requests.\nThis enables clients to take advantage of the browser cache and integrate with a CDN."),(0,s.yg)("p",null,"Because query identifiers are deterministic hashes, clients can generate them at runtime. No additional build steps are required."),(0,s.yg)("h2",{id:"setup"},"Setup"),(0,s.yg)("p",null,"To use Automatic persisted queries with GraphQLite, you may use\n",(0,s.yg)("inlineCode",{parentName:"p"},"useAutomaticPersistedQueries")," method when building your PSR-15 middleware:"),(0,s.yg)("pre",null,(0,s.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n// You need to provide a PSR compatible cache and a TTL for queries. The best cache would be some kind\n// of in-memory cache with a limit on number of entries to make sure your cache can't be maliciously spammed with queries.\n$builder->useAutomaticPersistedQueries($cache, new DateInterval('PT1H'));\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1edb88e5.3fcd7961.js b/assets/js/1edb88e5.24a4d76c.js similarity index 98% rename from assets/js/1edb88e5.3fcd7961.js rename to assets/js/1edb88e5.24a4d76c.js index 1cacc4c610..fefd85819c 100644 --- a/assets/js/1edb88e5.3fcd7961.js +++ b/assets/js/1edb88e5.24a4d76c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1245],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const i={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>N});var a=n(58168),r=n(96540),i=n(20053),l=n(23104),o=n(56347),u=n(57485),s=n(31682),c=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function p(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=p(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[u,s]=h({queryString:n,groupId:a}),[d,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),f=(()=>{const e=u??d;return y({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),m(e)}),[s,m,i]),tabValues:i}}var f=n(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,l.a_)(),p=e=>{const t=e.currentTarget,n=c.indexOf(t),a=s[n].value;a!==o&&(d(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},s.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:y,onClick:p},l,{className:(0,i.A)("tabs__item",g.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",g.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function N(e){const t=(0,f.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},711:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>l,default:()=>p,frontMatter:()=>i,metadata:()=>o,toc:()=>s});var a=n(58168),r=(n(96540),n(15680));n(67443),n(11470),n(19365);const i={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},l=void 0,o={unversionedId:"extend-input-type",id:"version-6.1/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.1/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/6.1/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/extend-input-type.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"docs",previous:{title:"Custom argument resolving",permalink:"/docs/6.1/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/6.1/multiple-output-types"}},u={},s=[],c={toc:s},d="wrapper";function p(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1245],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const i={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>N});var a=n(58168),r=n(96540),i=n(20053),l=n(23104),o=n(56347),u=n(57485),s=n(31682),c=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function p(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=p(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[u,s]=h({queryString:n,groupId:a}),[d,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),f=(()=>{const e=u??d;return y({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),m(e)}),[s,m,i]),tabValues:i}}var f=n(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,l.a_)(),p=e=>{const t=e.currentTarget,n=c.indexOf(t),a=s[n].value;a!==o&&(d(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},s.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:y,onClick:p},l,{className:(0,i.A)("tabs__item",g.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",g.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function N(e){const t=(0,f.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},711:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>l,default:()=>p,frontMatter:()=>i,metadata:()=>o,toc:()=>s});var a=n(58168),r=(n(96540),n(15680));n(67443),n(11470),n(19365);const i={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},l=void 0,o={unversionedId:"extend-input-type",id:"version-6.1/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.1/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/6.1/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/extend-input-type.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"docs",previous:{title:"Custom argument resolving",permalink:"/docs/6.1/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/6.1/multiple-output-types"}},u={},s=[],c={toc:s},d="wrapper";function p(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1f5af0f2.db54f2d9.js b/assets/js/1f5af0f2.918dcc76.js similarity index 97% rename from assets/js/1f5af0f2.db54f2d9.js rename to assets/js/1f5af0f2.918dcc76.js index f94e95429c..549f249886 100644 --- a/assets/js/1f5af0f2.db54f2d9.js +++ b/assets/js/1f5af0f2.918dcc76.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2962],{40611:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>d,frontMatter:()=>s,metadata:()=>i,toc:()=>l});var n=a(58168),p=(a(96540),a(15680));a(67443);const s={id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"},o=void 0,i={unversionedId:"custom-output-types",id:"version-4.0/custom-output-types",title:"Custom output types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-4.0/custom_output_types.md",sourceDirName:".",slug:"/custom-output-types",permalink:"/docs/4.0/custom-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/custom_output_types.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"}},u={},l=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3}],r={toc:l},y="wrapper";function d(e){let{components:t,...a}=e;return(0,p.yg)(y,(0,n.A)({},r,a,{components:t,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field(name="id")\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n')),(0,p.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,p.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,p.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,p.yg)("p",null,"GraphQL comes with an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,p.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID!")\n */\n')),(0,p.yg)("h2",{id:"usage"},"Usage"),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,p.yg)("p",null,"You can use the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Query")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Field")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@SourceField"))),(0,p.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,p.yg)("p",null,"In order to create a custom output type, you need to:"),(0,p.yg)("ol",null,(0,p.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,p.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,p.yg)("p",null,"You'll find more details on the ",(0,p.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,p.yg)("hr",null),(0,p.yg)("p",null,"In order to find existing types, the schema is using ",(0,p.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,p.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,p.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,p.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,p.yg)("p",null,"Any class extending ",(0,p.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,p.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,p.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,p.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,p.yg)("p",null,"The easiest way is to use a ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". This class is used to register custom output types."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper")," instance MUST be registered in your container and linked to a ",(0,p.yg)("inlineCode",{parentName:"p"},"CompositeTypeMapper"),"\nthat will aggregate all the type mappers of the application."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2962],{40611:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>d,frontMatter:()=>s,metadata:()=>i,toc:()=>l});var n=a(58168),p=(a(96540),a(15680));a(67443);const s={id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"},o=void 0,i={unversionedId:"custom-output-types",id:"version-4.0/custom-output-types",title:"Custom output types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-4.0/custom_output_types.md",sourceDirName:".",slug:"/custom-output-types",permalink:"/docs/4.0/custom-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/custom_output_types.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"}},u={},l=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3}],r={toc:l},y="wrapper";function d(e){let{components:t,...a}=e;return(0,p.yg)(y,(0,n.A)({},r,a,{components:t,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field(name="id")\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n')),(0,p.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,p.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,p.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,p.yg)("p",null,"GraphQL comes with an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,p.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID!")\n */\n')),(0,p.yg)("h2",{id:"usage"},"Usage"),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,p.yg)("p",null,"You can use the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Query")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Field")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@SourceField"))),(0,p.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,p.yg)("p",null,"In order to create a custom output type, you need to:"),(0,p.yg)("ol",null,(0,p.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,p.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,p.yg)("p",null,"You'll find more details on the ",(0,p.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,p.yg)("hr",null),(0,p.yg)("p",null,"In order to find existing types, the schema is using ",(0,p.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,p.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,p.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,p.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,p.yg)("p",null,"Any class extending ",(0,p.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,p.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,p.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,p.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,p.yg)("p",null,"The easiest way is to use a ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". This class is used to register custom output types."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper")," instance MUST be registered in your container and linked to a ",(0,p.yg)("inlineCode",{parentName:"p"},"CompositeTypeMapper"),"\nthat will aggregate all the type mappers of the application."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1f5e9707.9f12226d.js b/assets/js/1f5e9707.816a8926.js similarity index 94% rename from assets/js/1f5e9707.9f12226d.js rename to assets/js/1f5e9707.816a8926.js index 12d48c524f..128381904e 100644 --- a/assets/js/1f5e9707.9f12226d.js +++ b/assets/js/1f5e9707.816a8926.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7591],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var r=a(96540),t=a(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>$});var r=a(58168),t=a(96540),o=a(20053),i=a(23104),l=a(56347),s=a(57485),p=a(31682),c=a(89466);function u(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:r,default:t}}=e;return{value:n,label:a,attributes:r,default:t}}))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,p.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function h(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:a}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,p]=m({queryString:a,groupId:r}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(a);return[r,(0,t.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:r}),g=(()=>{const e=s??u;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),p(e),y(e)}),[p,y,o]),tabValues:o}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:s,tabValues:p}=e;const c=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),d=e=>{const n=e.currentTarget,a=c.indexOf(n),r=p[a].value;r!==l&&(u(n),s(r))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;n=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;n=c[a]??c[c.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},n)},p.map((e=>{let{value:n,label:a,attributes:i}=e;return t.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),a??n)})))}function w(e){let{lazy:n,children:a,selectedValue:r}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,r.A)({},e,n)),t.createElement(w,(0,r.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,r.A)({key:String(n)},e))}},32140:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>p,toc:()=>u});var r=a(58168),t=(a(96540),a(15680)),o=(a(67443),a(11470)),i=a(19365);const l={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},s=void 0,p={unversionedId:"other-frameworks",id:"version-6.0/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-6.0/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/6.0/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/other-frameworks.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"docs",previous:{title:"Universal service providers",permalink:"/docs/6.0/universal-service-providers"},next:{title:"Queries",permalink:"/docs/6.0/queries"}},c={},u=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],d={toc:u},h="wrapper";function m(e){let{components:n,...a}=e;return(0,t.yg)(h,(0,r.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Set a default InputType validator service to handle validation on all `Input` annotated types\n$factory->setInputTypeValidator($validator);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"$builder->setUrl(\"/graphql\"); // Modify the URL endpoint (defaults to /graphql)\n\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object.\n// Define your own formatter and error handlers for Webonyx.\n$config->setErrorFormatter([ExceptionHandler::class, 'errorFormatter']);\n$config->setErrorsHandler([ExceptionHandler::class, 'errorHandler']);\n\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n")),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,t.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n"))),(0,t.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")))),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new LazyContainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7591],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var r=a(96540),t=a(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>$});var r=a(58168),t=a(96540),o=a(20053),i=a(23104),l=a(56347),s=a(57485),c=a(31682),p=a(89466);function u(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:r,default:t}}=e;return{value:n,label:a,attributes:r,default:t}}))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function h(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:a}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,c]=m({queryString:a,groupId:r}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,p.Dv)(a);return[r,(0,t.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:r}),g=(()=>{const e=s??u;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),c(e),y(e)}),[c,y,o]),tabValues:o}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:s,tabValues:c}=e;const p=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),d=e=>{const n=e.currentTarget,a=p.indexOf(n),r=c[a].value;r!==l&&(u(n),s(r))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;n=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;n=p[a]??p[p.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return t.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>p.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),a??n)})))}function w(e){let{lazy:n,children:a,selectedValue:r}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,r.A)({},e,n)),t.createElement(w,(0,r.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,r.A)({key:String(n)},e))}},32140:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>c,toc:()=>u});var r=a(58168),t=(a(96540),a(15680)),o=(a(67443),a(11470)),i=a(19365);const l={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},s=void 0,c={unversionedId:"other-frameworks",id:"version-6.0/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-6.0/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/6.0/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/other-frameworks.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"docs",previous:{title:"Universal service providers",permalink:"/docs/6.0/universal-service-providers"},next:{title:"Queries",permalink:"/docs/6.0/queries"}},p={},u=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],d={toc:u},h="wrapper";function m(e){let{components:n,...a}=e;return(0,t.yg)(h,(0,r.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Set a default InputType validator service to handle validation on all `Input` annotated types\n$factory->setInputTypeValidator($validator);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"$builder->setUrl(\"/graphql\"); // Modify the URL endpoint (defaults to /graphql)\n\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object.\n// Define your own formatter and error handlers for Webonyx.\n$config->setErrorFormatter([ExceptionHandler::class, 'errorFormatter']);\n$config->setErrorsHandler([ExceptionHandler::class, 'errorHandler']);\n\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n")),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,t.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n"))),(0,t.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")))),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new LazyContainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2014e4e3.cdee6c53.js b/assets/js/2014e4e3.d25fa2df.js similarity index 96% rename from assets/js/2014e4e3.cdee6c53.js rename to assets/js/2014e4e3.d25fa2df.js index 158e01d6d8..ac7fb58bba 100644 --- a/assets/js/2014e4e3.cdee6c53.js +++ b/assets/js/2014e4e3.d25fa2df.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6481],{18650:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>s,frontMatter:()=>r,metadata:()=>o,toc:()=>d});var a=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},l=void 0,o={unversionedId:"extend-input-type",id:"extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/docs/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/next/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/extend-input-type.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"docs",previous:{title:"Custom argument resolving",permalink:"/docs/next/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/next/multiple-output-types"}},p={},d=[],u={toc:d},y="wrapper";function s(e){let{components:t,...n}=e;return(0,i.yg)(y,(0,a.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("small",null,"Available in GraphQLite 4.0+"),(0,i.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,i.yg)("code",null,"#[Factory]")," tag, ",(0,i.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,i.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,i.yg)("p",null,"Just like with output type (that can be ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/extend-type"},"extended using the ",(0,i.yg)("inlineCode",{parentName:"a"},"ExtendType")," attribute"),"), you can extend/modify\nan input type using the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Decorate]")," attribute."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Decorate]")," attribute to add additional fields to an input type that is already declared by a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Factory]")," attribute,\nor to modify the returned object."),(0,i.yg)("div",{class:"alert alert--info"},"The ",(0,i.yg)("code",null,"#[Decorate]")," attribute is very useful in scenarios where you cannot touch the ",(0,i.yg)("code",null,"#[Factory]")," method. This can happen if the ",(0,i.yg)("code",null,"#[Factory]")," method is defined in a third-party library or if the ",(0,i.yg)("code",null,"#[Factory]")," method is part of auto-generated code."),(0,i.yg)("p",null,"Let's assume you have a ",(0,i.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Factory]")," to create the input type."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")),(0,i.yg)("p",null,"Assuming you ",(0,i.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")),(0,i.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,i.yg)("p",null,"A few things to notice:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,i.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,i.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Decorate]")," attribute must contain a ",(0,i.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Factory]"),' attribute, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}s.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6481],{18650:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>s,frontMatter:()=>r,metadata:()=>o,toc:()=>d});var a=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},l=void 0,o={unversionedId:"extend-input-type",id:"extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/docs/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/next/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/extend-input-type.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"docs",previous:{title:"Custom argument resolving",permalink:"/docs/next/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/next/multiple-output-types"}},p={},d=[],u={toc:d},y="wrapper";function s(e){let{components:t,...n}=e;return(0,i.yg)(y,(0,a.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("small",null,"Available in GraphQLite 4.0+"),(0,i.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,i.yg)("code",null,"#[Factory]")," tag, ",(0,i.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,i.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,i.yg)("p",null,"Just like with output type (that can be ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/extend-type"},"extended using the ",(0,i.yg)("inlineCode",{parentName:"a"},"ExtendType")," attribute"),"), you can extend/modify\nan input type using the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Decorate]")," attribute."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Decorate]")," attribute to add additional fields to an input type that is already declared by a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Factory]")," attribute,\nor to modify the returned object."),(0,i.yg)("div",{class:"alert alert--info"},"The ",(0,i.yg)("code",null,"#[Decorate]")," attribute is very useful in scenarios where you cannot touch the ",(0,i.yg)("code",null,"#[Factory]")," method. This can happen if the ",(0,i.yg)("code",null,"#[Factory]")," method is defined in a third-party library or if the ",(0,i.yg)("code",null,"#[Factory]")," method is part of auto-generated code."),(0,i.yg)("p",null,"Let's assume you have a ",(0,i.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Factory]")," to create the input type."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")),(0,i.yg)("p",null,"Assuming you ",(0,i.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")),(0,i.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,i.yg)("p",null,"A few things to notice:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,i.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,i.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Decorate]")," attribute must contain a ",(0,i.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Factory]"),' attribute, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}s.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/20540af3.a43cc815.js b/assets/js/20540af3.26b7ee01.js similarity index 99% rename from assets/js/20540af3.a43cc815.js rename to assets/js/20540af3.26b7ee01.js index 3e1db0d3f4..5e0ee7009c 100644 --- a/assets/js/20540af3.a43cc815.js +++ b/assets/js/20540af3.26b7ee01.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4054],{94317:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>g,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>p,toc:()=>y});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,p={unversionedId:"annotations-reference",id:"version-7.0.0/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-7.0.0/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/annotations-reference.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/semver"}},g={},y=[{value:"@Query",id:"query",level:2},{value:"@Mutation",id:"mutation",level:2},{value:"@Subscription",id:"subscription",level:2},{value:"@Type",id:"type",level:2},{value:"@ExtendType",id:"extendtype",level:2},{value:"@Input",id:"input",level:2},{value:"@Field",id:"field",level:2},{value:"@SourceField",id:"sourcefield",level:2},{value:"@MagicField",id:"magicfield",level:2},{value:"@Prefetch",id:"prefetch",level:2},{value:"@Logged",id:"logged",level:2},{value:"@Right",id:"right",level:2},{value:"@FailWith",id:"failwith",level:2},{value:"@HideIfUnauthorized",id:"hideifunauthorized",level:2},{value:"@InjectUser",id:"injectuser",level:2},{value:"@Security",id:"security",level:2},{value:"@Factory",id:"factory",level:2},{value:"@UseInputType",id:"useinputtype",level:2},{value:"@Decorate",id:"decorate",level:2},{value:"@Autowire",id:"autowire",level:2},{value:"@HideParameter",id:"hideparameter",level:2},{value:"@Cost",id:"cost",level:2},{value:"@Validate",id:"validate",level:2},{value:"@Assertion",id:"assertion",level:2},{value:"@EnumType",id:"enumtype",level:2}],o={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},o,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query"},"@Query"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation"},"@Mutation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"subscription"},"@Subscription"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Subscription")," annotation is used to declare a GraphQL subscription."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the subscription. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Defines the GraphQL output type that will be sent for the subscription.")))),(0,l.yg)("h2",{id:"type"},"@Type"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type. This is used with standard output\ntypes, as well as enum types. For input types, use the ",(0,l.yg)("a",{parentName:"p",href:"#input-annotation"},"@Input annotation")," directly on the input type or a ",(0,l.yg)("a",{parentName:"p",href:"#factory-annotation"},"@Factory annoation")," to make/return an input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class/enum for the actual type. If no "class" attribute is passed, the type applies to the current class/enum. The current class/enum is assumed to be an entity (not service). If the "class" attribute ',(0,l.yg)("em",{parentName:"td"},"is passed"),", ",(0,l.yg)("a",{parentName:"td",href:"/docs/external-type-declaration"},"the class/enum annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," becomes a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype"},"@ExtendType"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input"},"@Input"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Name of the input type represented in your GraphQL schema. Defaults to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," ",(0,l.yg)("em",{parentName:"td"},"only if")," the name is not specified. If ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is specified, this will default to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", so must also be included for ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," when ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation/subscription. This primarily applies to nullable fields.")))),(0,l.yg)("h2",{id:"field"},"@Field"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield"},"@SourceField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield"},"@MagicField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If not set, no description will be shown.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"prefetch"},"@Prefetch"),(0,l.yg)("p",null,"Marks field parameter to be used for ",(0,l.yg)("a",{parentName:"p",href:"/docs/prefetch-method"},"prefetching"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": parameters of methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"callable"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"callable"),(0,l.yg)("td",{parentName:"tr",align:null},"Name of the prefetch method (in same class) or a full callable, either a static method or regular service from the container")))),(0,l.yg)("h2",{id:"logged"},"@Logged"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right"},"@Right"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith"},"@FailWith"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery/mutation/subscription/field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized"},"@HideIfUnauthorized"),(0,l.yg)("div",{class:"alert alert--warning"},"This annotation only works when a Schema is used to handle exactly one use request. If you serve your GraphQL API from long-running standalone servers (like Laravel Octane, Swoole, RoadRunner etc) and share the same Schema instance between multiple requests, please avoid using @HideIfUnauthorized."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query/mutation/subscription/field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser"},"@InjectUser"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery/mutation/subscription/field."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/authentication-authorization"},"the authentication and authorization page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security"},"@Security"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory"},"@Factory"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype"},"@UseInputType"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate"},"@Decorate"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire"},"@Autowire"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter"},"@HideParameter"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"cost"},"@Cost"),(0,l.yg)("p",null,"Sets complexity and multipliers on fields for ",(0,l.yg)("a",{parentName:"p",href:"/docs/operation-complexity#static-request-analysis"},"automatic query complexity"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"complexity")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"int"),(0,l.yg)("td",{parentName:"tr",align:null},"Complexity for that field")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"multipliers")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},"Names of fields by value of which complexity will be multiplied")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"defaultMultiplier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"int"),(0,l.yg)("td",{parentName:"tr",align:null},"Default multiplier value if all multipliers are missing/null")))),(0,l.yg)("h2",{id:"validate"},"@Validate"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion"},"@Assertion"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype"},(0,l.yg)("del",{parentName:"h2"},"@EnumType")),(0,l.yg)("p",null,(0,l.yg)("em",{parentName:"p"},"Deprecated: Use ",(0,l.yg)("a",{parentName:"em",href:"https://www.php.net/manual/en/language.types.enumerations.php"},"PHP 8.1's native Enums")," instead with a ",(0,l.yg)("a",{parentName:"em",href:"#type-annotation"},"@Type"),".")),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4054],{94317:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>g,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>p,toc:()=>y});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,p={unversionedId:"annotations-reference",id:"version-7.0.0/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-7.0.0/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/annotations-reference.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/semver"}},g={},y=[{value:"@Query",id:"query",level:2},{value:"@Mutation",id:"mutation",level:2},{value:"@Subscription",id:"subscription",level:2},{value:"@Type",id:"type",level:2},{value:"@ExtendType",id:"extendtype",level:2},{value:"@Input",id:"input",level:2},{value:"@Field",id:"field",level:2},{value:"@SourceField",id:"sourcefield",level:2},{value:"@MagicField",id:"magicfield",level:2},{value:"@Prefetch",id:"prefetch",level:2},{value:"@Logged",id:"logged",level:2},{value:"@Right",id:"right",level:2},{value:"@FailWith",id:"failwith",level:2},{value:"@HideIfUnauthorized",id:"hideifunauthorized",level:2},{value:"@InjectUser",id:"injectuser",level:2},{value:"@Security",id:"security",level:2},{value:"@Factory",id:"factory",level:2},{value:"@UseInputType",id:"useinputtype",level:2},{value:"@Decorate",id:"decorate",level:2},{value:"@Autowire",id:"autowire",level:2},{value:"@HideParameter",id:"hideparameter",level:2},{value:"@Cost",id:"cost",level:2},{value:"@Validate",id:"validate",level:2},{value:"@Assertion",id:"assertion",level:2},{value:"@EnumType",id:"enumtype",level:2}],o={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},o,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query"},"@Query"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation"},"@Mutation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"subscription"},"@Subscription"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Subscription")," annotation is used to declare a GraphQL subscription."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the subscription. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Defines the GraphQL output type that will be sent for the subscription.")))),(0,l.yg)("h2",{id:"type"},"@Type"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type. This is used with standard output\ntypes, as well as enum types. For input types, use the ",(0,l.yg)("a",{parentName:"p",href:"#input-annotation"},"@Input annotation")," directly on the input type or a ",(0,l.yg)("a",{parentName:"p",href:"#factory-annotation"},"@Factory annoation")," to make/return an input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class/enum for the actual type. If no "class" attribute is passed, the type applies to the current class/enum. The current class/enum is assumed to be an entity (not service). If the "class" attribute ',(0,l.yg)("em",{parentName:"td"},"is passed"),", ",(0,l.yg)("a",{parentName:"td",href:"/docs/external-type-declaration"},"the class/enum annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," becomes a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype"},"@ExtendType"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input"},"@Input"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Name of the input type represented in your GraphQL schema. Defaults to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," ",(0,l.yg)("em",{parentName:"td"},"only if")," the name is not specified. If ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is specified, this will default to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", so must also be included for ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," when ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation/subscription. This primarily applies to nullable fields.")))),(0,l.yg)("h2",{id:"field"},"@Field"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield"},"@SourceField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield"},"@MagicField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If not set, no description will be shown.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"prefetch"},"@Prefetch"),(0,l.yg)("p",null,"Marks field parameter to be used for ",(0,l.yg)("a",{parentName:"p",href:"/docs/prefetch-method"},"prefetching"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": parameters of methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"callable"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"callable"),(0,l.yg)("td",{parentName:"tr",align:null},"Name of the prefetch method (in same class) or a full callable, either a static method or regular service from the container")))),(0,l.yg)("h2",{id:"logged"},"@Logged"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right"},"@Right"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith"},"@FailWith"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery/mutation/subscription/field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized"},"@HideIfUnauthorized"),(0,l.yg)("div",{class:"alert alert--warning"},"This annotation only works when a Schema is used to handle exactly one use request. If you serve your GraphQL API from long-running standalone servers (like Laravel Octane, Swoole, RoadRunner etc) and share the same Schema instance between multiple requests, please avoid using @HideIfUnauthorized."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query/mutation/subscription/field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser"},"@InjectUser"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery/mutation/subscription/field."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/authentication-authorization"},"the authentication and authorization page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security"},"@Security"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory"},"@Factory"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype"},"@UseInputType"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate"},"@Decorate"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire"},"@Autowire"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter"},"@HideParameter"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"cost"},"@Cost"),(0,l.yg)("p",null,"Sets complexity and multipliers on fields for ",(0,l.yg)("a",{parentName:"p",href:"/docs/operation-complexity#static-request-analysis"},"automatic query complexity"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"complexity")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"int"),(0,l.yg)("td",{parentName:"tr",align:null},"Complexity for that field")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"multipliers")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},"Names of fields by value of which complexity will be multiplied")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"defaultMultiplier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"int"),(0,l.yg)("td",{parentName:"tr",align:null},"Default multiplier value if all multipliers are missing/null")))),(0,l.yg)("h2",{id:"validate"},"@Validate"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion"},"@Assertion"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype"},(0,l.yg)("del",{parentName:"h2"},"@EnumType")),(0,l.yg)("p",null,(0,l.yg)("em",{parentName:"p"},"Deprecated: Use ",(0,l.yg)("a",{parentName:"em",href:"https://www.php.net/manual/en/language.types.enumerations.php"},"PHP 8.1's native Enums")," instead with a ",(0,l.yg)("a",{parentName:"em",href:"#type-annotation"},"@Type"),".")),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/21637dff.b4c6abe5.js b/assets/js/21637dff.327925d3.js similarity index 94% rename from assets/js/21637dff.b4c6abe5.js rename to assets/js/21637dff.327925d3.js index ee8aa5bc0b..e2a81c8302 100644 --- a/assets/js/21637dff.b4c6abe5.js +++ b/assets/js/21637dff.327925d3.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9032],{19365:(e,n,t)=>{t.d(n,{A:()=>o});var a=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>I});var a=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function p(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??d(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function h(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=g({queryString:t,groupId:a}),[d,m]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),y=(()=>{const e=u??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),m(e)}),[s,m,i]),tabValues:i}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(d(n),u(a))},h=e=>{let n=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function N(e){const n=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function I(e){const n=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(n)},e))}},41080:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>g,frontMatter:()=>l,metadata:()=>s,toc:()=>d});var a=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},u=void 0,s={unversionedId:"authentication-authorization",id:"version-4.2/authentication-authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-4.2/authentication-authorization.mdx",sourceDirName:".",slug:"/authentication-authorization",permalink:"/docs/4.2/authentication-authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/authentication-authorization.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},sidebar:"version-4.2/docs",previous:{title:"User input validation",permalink:"/docs/4.2/validation"},next:{title:"Fine grained security",permalink:"/docs/4.2/fine-grained-security"}},c={},d=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],p={toc:d},h="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(h,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.2/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9032],{19365:(e,n,t)=>{t.d(n,{A:()=>o});var a=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>I});var a=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function h(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??d(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function p(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=h(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!p({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=g({queryString:t,groupId:a}),[d,m]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),y=(()=>{const e=u??d;return p({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!p({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),m(e)}),[s,m,i]),tabValues:i}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(d(n),u(a))},p=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:p,onClick:h},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function N(e){const n=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function I(e){const n=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(n)},e))}},41080:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>g,frontMatter:()=>l,metadata:()=>s,toc:()=>d});var a=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},u=void 0,s={unversionedId:"authentication-authorization",id:"version-4.2/authentication-authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-4.2/authentication-authorization.mdx",sourceDirName:".",slug:"/authentication-authorization",permalink:"/docs/4.2/authentication-authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/authentication-authorization.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},sidebar:"version-4.2/docs",previous:{title:"User input validation",permalink:"/docs/4.2/validation"},next:{title:"Fine grained security",permalink:"/docs/4.2/fine-grained-security"}},c={},d=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],h={toc:d},p="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(p,(0,a.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.2/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/21a7a3b0.b7e5fa05.js b/assets/js/21a7a3b0.f2e2fc55.js similarity index 58% rename from assets/js/21a7a3b0.b7e5fa05.js rename to assets/js/21a7a3b0.f2e2fc55.js index 269a7460c1..a7ee5fe36f 100644 --- a/assets/js/21a7a3b0.b7e5fa05.js +++ b/assets/js/21a7a3b0.f2e2fc55.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3382],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>A});var n=a(58168),r=a(96540),i=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(n.location.search);t.set(i,e),n.replace({...n.location,search:t.toString()})}),[i,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:i}))),[s,u]=h({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,i]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},o,{className:(0,i.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",y.tabList)},r.createElement(v,(0,n.A)({},e,t)),r.createElement(b,(0,n.A)({},e,t)))}function A(e){const t=(0,f.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},67708:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>p,frontMatter:()=>i,metadata:()=>l,toc:()=>u});var n=a(58168),r=(a(96540),a(15680));a(67443),a(11470),a(19365);const i={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},o=void 0,l={unversionedId:"autowiring",id:"version-6.1/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-6.1/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/6.1/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/autowiring.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"docs",previous:{title:"Type mapping",permalink:"/docs/6.1/type-mapping"},next:{title:"Extending a type",permalink:"/docs/6.1/extend-type"}},s={},u=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],c={toc:u},d="wrapper";function p(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n')),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3382],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),i=a(20053);const r={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,i.A)(r.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>A});var n=a(58168),i=a(96540),r=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return i.Children.map(e,(e=>{if(!e||(0,i.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:i}}=e;return{value:t,label:a,attributes:n,default:i}}))}function p(e){const{values:t,children:a}=e;return(0,i.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),r=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(r),(0,i.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(n.location.search);t.set(r,e),n.replace({...n.location,search:t.toString()})}),[r,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,r=p(e),[o,l]=(0,i.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:r}))),[s,u]=h({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,r]=(0,c.Dv)(a);return[n,(0,i.useCallback)((e=>{a&&r.set(e)}),[a,r])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:r})?e:null})();(0,i.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:o,selectValue:(0,i.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,r]),tabValues:r}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return i.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},o,{className:(0,r.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const r=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===n));return e?(0,i.cloneElement)(e,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,i.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return i.createElement("div",{className:(0,r.A)("tabs-container",y.tabList)},i.createElement(v,(0,n.A)({},e,t)),i.createElement(b,(0,n.A)({},e,t)))}function A(e){const t=(0,f.A)();return i.createElement(w,(0,n.A)({key:String(t)},e))}},67708:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>p,frontMatter:()=>r,metadata:()=>l,toc:()=>u});var n=a(58168),i=(a(96540),a(15680));a(67443),a(11470),a(19365);const r={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},o=void 0,l={unversionedId:"autowiring",id:"version-6.1/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-6.1/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/6.1/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/autowiring.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"docs",previous:{title:"Type mapping",permalink:"/docs/6.1/type-mapping"},next:{title:"Extending a type",permalink:"/docs/6.1/extend-type"}},s={},u=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],c={toc:u},d="wrapper";function p(e){let{components:t,...a}=e;return(0,i.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,i.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,i.yg)("p",null,"Most of the time, your ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,i.yg)("h2",{id:"sample"},"Sample"),(0,i.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")),(0,i.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,i.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,i.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,i.yg)("h2",{id:"best-practices"},"Best practices"),(0,i.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,i.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,i.yg)("pre",null,(0,i.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,i.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,i.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,i.yg)("pre",null,(0,i.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,i.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,i.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,i.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n')),(0,i.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,i.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,i.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,i.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,i.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,i.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,i.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,i.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/21cde469.af4cb392.js b/assets/js/21cde469.97a411b3.js similarity index 99% rename from assets/js/21cde469.af4cb392.js rename to assets/js/21cde469.97a411b3.js index 7d3de2d7e2..d26b2d53ca 100644 --- a/assets/js/21cde469.af4cb392.js +++ b/assets/js/21cde469.97a411b3.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1653],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},37088:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-4.2/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-4.2/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/4.2/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/error-handling.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"version-4.2/docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/4.2/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/4.2/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/4.2/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/4.2/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1653],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},37088:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-4.2/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-4.2/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/4.2/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/error-handling.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"version-4.2/docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/4.2/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/4.2/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/4.2/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/4.2/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/22e1e32f.891fb0d4.js b/assets/js/22e1e32f.fd1edf1e.js similarity index 98% rename from assets/js/22e1e32f.891fb0d4.js rename to assets/js/22e1e32f.fd1edf1e.js index f99e2e3772..d8f30b51c6 100644 --- a/assets/js/22e1e32f.891fb0d4.js +++ b/assets/js/22e1e32f.fd1edf1e.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3613],{16028:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>d,contentTitle:()=>r,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var t=n(58168),i=(n(96540),n(15680));n(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"version-6.0/changelog",title:"Changelog",description:"5.0.0",source:"@site/versioned_docs/version-6.0/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/6.0/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/CHANGELOG.md",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"docs",previous:{title:"Semantic versioning",permalink:"/docs/6.0/semver"}},d={},p=[{value:"5.0.0",id:"500",level:2},{value:"Dependencies:",id:"dependencies",level:4},{value:"4.3.0",id:"430",level:2},{value:"Breaking change:",id:"breaking-change",level:4},{value:"Minor changes:",id:"minor-changes",level:4},{value:"4.2.0",id:"420",level:2},{value:"Breaking change:",id:"breaking-change-1",level:4},{value:"New features:",id:"new-features",level:4},{value:"4.1.0",id:"410",level:2},{value:"Breaking change:",id:"breaking-change-2",level:4},{value:"New features:",id:"new-features-1",level:4},{value:"Minor changes:",id:"minor-changes-1",level:4},{value:"Miscellaneous:",id:"miscellaneous",level:4},{value:"4.0.0",id:"400",level:2},{value:"New features:",id:"new-features-2",level:4},{value:"Symfony:",id:"symfony",level:4},{value:"Laravel:",id:"laravel",level:4},{value:"Internals:",id:"internals",level:4}],s={toc:p},g="wrapper";function u(e){let{components:a,...n}=e;return(0,i.yg)(g,(0,t.A)({},s,n,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"500"},"5.0.0"),(0,i.yg)("h4",{id:"dependencies"},"Dependencies:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Upgraded to using version 14.9 of ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/webonyx/graphql-php"},"webonyx/graphql-php"))),(0,i.yg)("h2",{id:"430"},"4.3.0"),(0,i.yg)("h4",{id:"breaking-change"},"Breaking change:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The method ",(0,i.yg)("inlineCode",{parentName:"li"},"setAnnotationCacheDir($directory)")," has been removed from the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),". The annotation\ncache will use your ",(0,i.yg)("inlineCode",{parentName:"li"},"Psr\\SimpleCache\\CacheInterface")," compliant cache handler set through the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),"\nconstructor.")),(0,i.yg)("h4",{id:"minor-changes"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Removed dependency for doctrine/cache and unified some of the cache layers following a PSR interface."),(0,i.yg)("li",{parentName:"ul"},"Cleaned up some of the documentation in an attempt to get things accurate with versioned releases.")),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h4",{id:"breaking-change-1"},"Breaking change:"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h4",{id:"new-features"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"@Type")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/input-types#input-attribute"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Logged"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h4",{id:"breaking-change-2"},"Breaking change:"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h4",{id:"new-features-1"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h4",{id:"minor-changes-1"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h4",{id:"miscellaneous"},"Miscellaneous:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h4",{id:"new-features-2"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h4",{id:"symfony"},"Symfony:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h4",{id:"laravel"},"Laravel:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h4",{id:"internals"},"Internals:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3613],{16028:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>d,contentTitle:()=>r,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var t=n(58168),i=(n(96540),n(15680));n(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"version-6.0/changelog",title:"Changelog",description:"5.0.0",source:"@site/versioned_docs/version-6.0/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/6.0/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/CHANGELOG.md",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"docs",previous:{title:"Semantic versioning",permalink:"/docs/6.0/semver"}},d={},p=[{value:"5.0.0",id:"500",level:2},{value:"Dependencies:",id:"dependencies",level:4},{value:"4.3.0",id:"430",level:2},{value:"Breaking change:",id:"breaking-change",level:4},{value:"Minor changes:",id:"minor-changes",level:4},{value:"4.2.0",id:"420",level:2},{value:"Breaking change:",id:"breaking-change-1",level:4},{value:"New features:",id:"new-features",level:4},{value:"4.1.0",id:"410",level:2},{value:"Breaking change:",id:"breaking-change-2",level:4},{value:"New features:",id:"new-features-1",level:4},{value:"Minor changes:",id:"minor-changes-1",level:4},{value:"Miscellaneous:",id:"miscellaneous",level:4},{value:"4.0.0",id:"400",level:2},{value:"New features:",id:"new-features-2",level:4},{value:"Symfony:",id:"symfony",level:4},{value:"Laravel:",id:"laravel",level:4},{value:"Internals:",id:"internals",level:4}],s={toc:p},g="wrapper";function u(e){let{components:a,...n}=e;return(0,i.yg)(g,(0,t.A)({},s,n,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"500"},"5.0.0"),(0,i.yg)("h4",{id:"dependencies"},"Dependencies:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Upgraded to using version 14.9 of ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/webonyx/graphql-php"},"webonyx/graphql-php"))),(0,i.yg)("h2",{id:"430"},"4.3.0"),(0,i.yg)("h4",{id:"breaking-change"},"Breaking change:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The method ",(0,i.yg)("inlineCode",{parentName:"li"},"setAnnotationCacheDir($directory)")," has been removed from the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),". The annotation\ncache will use your ",(0,i.yg)("inlineCode",{parentName:"li"},"Psr\\SimpleCache\\CacheInterface")," compliant cache handler set through the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),"\nconstructor.")),(0,i.yg)("h4",{id:"minor-changes"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Removed dependency for doctrine/cache and unified some of the cache layers following a PSR interface."),(0,i.yg)("li",{parentName:"ul"},"Cleaned up some of the documentation in an attempt to get things accurate with versioned releases.")),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h4",{id:"breaking-change-1"},"Breaking change:"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h4",{id:"new-features"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"@Type")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/input-types#input-attribute"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Logged"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h4",{id:"breaking-change-2"},"Breaking change:"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h4",{id:"new-features-1"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h4",{id:"minor-changes-1"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h4",{id:"miscellaneous"},"Miscellaneous:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h4",{id:"new-features-2"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h4",{id:"symfony"},"Symfony:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h4",{id:"laravel"},"Laravel:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h4",{id:"internals"},"Internals:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/232afa3a.e9a3d7db.js b/assets/js/232afa3a.77394b96.js similarity index 89% rename from assets/js/232afa3a.e9a3d7db.js rename to assets/js/232afa3a.77394b96.js index 82a96f10e1..68c3f1458a 100644 --- a/assets/js/232afa3a.e9a3d7db.js +++ b/assets/js/232afa3a.77394b96.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4762],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),u=a(56347),i=a(57485),s=a(31682),p=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function f(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=c(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,s]=h({queryString:a,groupId:n}),[d,f]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),g=(()=>{const e=i??d;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{g&&u(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),s(e),f(e)}),[s,f,l]),tabValues:l}}var g=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:u,selectValue:i,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==u&&(d(t),i(n))},m=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:c},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":u===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},41002:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>s,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const u={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},i=void 0,s={unversionedId:"file-uploads",id:"version-7.0.0/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-7.0.0/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/file-uploads.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"docs",previous:{title:"Automatic persisted queries",permalink:"/docs/automatic-persisted-queries"},next:{title:"Pagination",permalink:"/docs/pagination"}},p={},d=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],c={toc:d},m="wrapper";function h(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,r.yg)("p",null,"You must start by installing this package:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,r.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,r.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,r.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,r.yg)("p",null,"In order to use this, you must first be sure that the ",(0,r.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,r.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,r.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,r.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")))),(0,r.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,r.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4762],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),u=a(56347),i=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function f(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,s]=h({queryString:a,groupId:n}),[c,f]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),g=(()=>{const e=i??c;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{g&&u(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),s(e),f(e)}),[s,f,l]),tabValues:l}}var g=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:u,selectValue:i,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==u&&(c(t),i(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":u===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},41002:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const u={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},i=void 0,s={unversionedId:"file-uploads",id:"version-7.0.0/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-7.0.0/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/file-uploads.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"docs",previous:{title:"Automatic persisted queries",permalink:"/docs/automatic-persisted-queries"},next:{title:"Pagination",permalink:"/docs/pagination"}},p={},c=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],d={toc:c},m="wrapper";function h(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,r.yg)("p",null,"You must start by installing this package:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,r.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,r.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,r.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,r.yg)("p",null,"In order to use this, you must first be sure that the ",(0,r.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,r.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,r.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,r.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")))),(0,r.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,r.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2355609d.3e569e61.js b/assets/js/2355609d.ff4b6802.js similarity index 92% rename from assets/js/2355609d.3e569e61.js rename to assets/js/2355609d.ff4b6802.js index f9a2731397..f62f692d51 100644 --- a/assets/js/2355609d.3e569e61.js +++ b/assets/js/2355609d.ff4b6802.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6145],{24197:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-4.3/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-4.3/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/4.3/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/universal-service-providers.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"version-4.3/docs",previous:{title:"Laravel package",permalink:"/docs/4.3/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/4.3/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/4.3/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6145],{24197:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>p});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-4.3/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-4.3/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/4.3/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/universal-service-providers.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"version-4.3/docs",previous:{title:"Laravel package",permalink:"/docs/4.3/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/4.3/other-frameworks"}},l={},p=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:p},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/4.3/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/23794275.2ada9f7e.js b/assets/js/23794275.9a8d8d95.js similarity index 99% rename from assets/js/23794275.2ada9f7e.js rename to assets/js/23794275.9a8d8d95.js index a03e4d5f42..cd7104c76c 100644 --- a/assets/js/23794275.2ada9f7e.js +++ b/assets/js/23794275.9a8d8d95.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1776],{19365:(e,n,a)=>{a.d(n,{A:()=>r});var t=a(96540),p=a(20053);const l={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:a,className:r}=e;return t.createElement("div",{role:"tabpanel",className:(0,p.A)(l.tabItem,r),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>N});var t=a(58168),p=a(96540),l=a(20053),r=a(23104),s=a(56347),i=a(57485),u=a(31682),o=a(89466);function c(e){return function(e){return p.Children.map(e,(e=>{if(!e||(0,p.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:p}}=e;return{value:n,label:a,attributes:t,default:p}}))}function m(e){const{values:n,children:a}=e;return(0,p.useMemo)((()=>{const e=n??c(a);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function y(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function d(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,i.aZ)(l),(0,p.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function g(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[r,s]=(0,p.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[i,u]=d({queryString:a,groupId:t}),[c,g]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,p.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=i??c;return y({value:e,tabValues:l})?e:null})();(0,p.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:r,selectValue:(0,p.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const o=[],{blockElementScrollPositionUntilNextRender:c}=(0,r.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=u[a].value;t!==s&&(c(n),i(t))},y=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return p.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},u.map((e=>{let{value:n,label:a,attributes:r}=e;return p.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:y,onClick:m},r,{className:(0,l.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,p.cloneElement)(e,{className:"margin-top--md"}):null}return p.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,p.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function T(e){const n=g(e);return p.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},p.createElement(b,(0,t.A)({},e,n)),p.createElement(v,(0,t.A)({},e,n)))}function N(e){const n=(0,h.A)();return p.createElement(T,(0,t.A)({key:String(n)},e))}},22793:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>u,toc:()=>c});var t=a(58168),p=(a(96540),a(15680)),l=(a(67443),a(11470)),r=a(19365);const s={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},i=void 0,u={unversionedId:"type-mapping",id:"version-6.0/type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-6.0/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/6.0/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/type-mapping.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"docs",previous:{title:"Mutations",permalink:"/docs/6.0/mutations"},next:{title:"Autowiring services",permalink:"/docs/6.0/autowiring"}},o={},c=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Enum types with myclabs/php-enum",id:"enum-types-with-myclabsphp-enum",level:3},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],m={toc:c},y="wrapper";function d(e){let{components:n,...a}=e;return(0,p.yg)(y,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/6.0/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(name="MyProduct")\n */\nclass Product { /* ... */ }\n')))),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')))),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')))),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/6.0/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")))),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")))),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")))),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"Union types for return are supported in GraphQLite as of version 6.0:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\npublic function companyOrContact(int $id): Company|Contact\n{\n // Some code that returns a company or a contact.\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")))),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("p",null,"PHP 8.1 introduced native support for Enums. GraphQLite now also supports native enums as of version 5.1."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nenum Status: string\n{\n case ON = 'on';\n case OFF = 'off';\n case PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return User[]\n */\n#[Query]\npublic function users(Status $status): array\n{\n if ($status === Status::ON) {\n // Your logic\n }\n // ...\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: Status!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"name")," property on the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," annotation:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'namespace Model\\User;\n\n#[Type(name: "UserStatus")]\nenum Status: string\n{\n // ...\n}\n')),(0,p.yg)("h3",{id:"enum-types-with-myclabsphp-enum"},"Enum types with myclabs/php-enum"),(0,p.yg)("div",{class:"alert alert--danger"},"This implementation is now deprecated and will be removed in the future. You are advised to use native enums instead."),(0,p.yg)("p",null,(0,p.yg)("em",{parentName:"p"},"Prior to version 5.1, GraphQLite only supported Enums through the 3rd party library, ",(0,p.yg)("a",{parentName:"em",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),". If you'd like to use this implementation you'll first need to add this library as a dependency to your application.")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[]\n */\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')))),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n/**\n * @EnumType(name="UserStatus")\n */\nclass StatusEnum extends Enum\n{\n // ...\n}\n')))),(0,p.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,p.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,p.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,p.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation. Note that a description (reason) is required for the annotation to be rendered."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n * @deprecated use field `name` instead\n */\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,p.yg)("p",null,"This will add the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,p.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,p.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,p.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,p.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,p.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1776],{19365:(e,n,a)=>{a.d(n,{A:()=>r});var t=a(96540),p=a(20053);const l={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:a,className:r}=e;return t.createElement("div",{role:"tabpanel",className:(0,p.A)(l.tabItem,r),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>N});var t=a(58168),p=a(96540),l=a(20053),r=a(23104),s=a(56347),i=a(57485),u=a(31682),o=a(89466);function c(e){return function(e){return p.Children.map(e,(e=>{if(!e||(0,p.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:p}}=e;return{value:n,label:a,attributes:t,default:p}}))}function m(e){const{values:n,children:a}=e;return(0,p.useMemo)((()=>{const e=n??c(a);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function y(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function d(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,i.aZ)(l),(0,p.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function g(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[r,s]=(0,p.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[i,u]=d({queryString:a,groupId:t}),[c,g]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,p.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=i??c;return y({value:e,tabValues:l})?e:null})();(0,p.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:r,selectValue:(0,p.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const o=[],{blockElementScrollPositionUntilNextRender:c}=(0,r.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=u[a].value;t!==s&&(c(n),i(t))},y=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return p.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},u.map((e=>{let{value:n,label:a,attributes:r}=e;return p.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:y,onClick:m},r,{className:(0,l.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,p.cloneElement)(e,{className:"margin-top--md"}):null}return p.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,p.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function T(e){const n=g(e);return p.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},p.createElement(b,(0,t.A)({},e,n)),p.createElement(v,(0,t.A)({},e,n)))}function N(e){const n=(0,h.A)();return p.createElement(T,(0,t.A)({key:String(n)},e))}},22793:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>u,toc:()=>c});var t=a(58168),p=(a(96540),a(15680)),l=(a(67443),a(11470)),r=a(19365);const s={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},i=void 0,u={unversionedId:"type-mapping",id:"version-6.0/type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-6.0/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/6.0/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/type-mapping.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"docs",previous:{title:"Mutations",permalink:"/docs/6.0/mutations"},next:{title:"Autowiring services",permalink:"/docs/6.0/autowiring"}},o={},c=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Enum types with myclabs/php-enum",id:"enum-types-with-myclabsphp-enum",level:3},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],m={toc:c},y="wrapper";function d(e){let{components:n,...a}=e;return(0,p.yg)(y,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/6.0/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(name="MyProduct")\n */\nclass Product { /* ... */ }\n')))),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')))),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')))),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/6.0/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")))),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")))),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")))),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"Union types for return are supported in GraphQLite as of version 6.0:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\npublic function companyOrContact(int $id): Company|Contact\n{\n // Some code that returns a company or a contact.\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")))),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("p",null,"PHP 8.1 introduced native support for Enums. GraphQLite now also supports native enums as of version 5.1."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nenum Status: string\n{\n case ON = 'on';\n case OFF = 'off';\n case PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return User[]\n */\n#[Query]\npublic function users(Status $status): array\n{\n if ($status === Status::ON) {\n // Your logic\n }\n // ...\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: Status!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"name")," property on the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," annotation:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'namespace Model\\User;\n\n#[Type(name: "UserStatus")]\nenum Status: string\n{\n // ...\n}\n')),(0,p.yg)("h3",{id:"enum-types-with-myclabsphp-enum"},"Enum types with myclabs/php-enum"),(0,p.yg)("div",{class:"alert alert--danger"},"This implementation is now deprecated and will be removed in the future. You are advised to use native enums instead."),(0,p.yg)("p",null,(0,p.yg)("em",{parentName:"p"},"Prior to version 5.1, GraphQLite only supported Enums through the 3rd party library, ",(0,p.yg)("a",{parentName:"em",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),". If you'd like to use this implementation you'll first need to add this library as a dependency to your application.")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[]\n */\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')))),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n/**\n * @EnumType(name="UserStatus")\n */\nclass StatusEnum extends Enum\n{\n // ...\n}\n')))),(0,p.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,p.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,p.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,p.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation. Note that a description (reason) is required for the annotation to be rendered."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n * @deprecated use field `name` instead\n */\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,p.yg)("p",null,"This will add the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,p.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,p.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,p.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,p.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,p.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/23a8ac29.b99ca107.js b/assets/js/23a8ac29.8abcea32.js similarity index 99% rename from assets/js/23a8ac29.b99ca107.js rename to assets/js/23a8ac29.8abcea32.js index bf2cd3dc1a..bd008a2142 100644 --- a/assets/js/23a8ac29.b99ca107.js +++ b/assets/js/23a8ac29.8abcea32.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1950],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>V});var n=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),d=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function c(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??p(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function m(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:t}=e;const n=(0,l.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function y(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!m({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=h({queryString:t,groupId:n}),[p,y]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,d.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),g=(()=>{const e=s??p;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,i]),tabValues:i}}var g=t(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:a,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),c=e=>{const a=e.currentTarget,t=d.indexOf(a),n=u[t].value;n!==l&&(p(a),s(n))},m=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const t=d.indexOf(e.currentTarget)+1;a=d[t]??d[0];break}case"ArrowLeft":{const t=d.indexOf(e.currentTarget)-1;a=d[t]??d[d.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===a?0:-1,"aria-selected":l===a,key:a,ref:e=>d.push(e),onKeyDown:m,onClick:c},o,{className:(0,i.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=y(e);return r.createElement("div",{className:(0,i.A)("tabs-container",v.tabList)},r.createElement(f,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function V(e){const a=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},2192:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-5.0/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-5.0/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/5.0/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/validation.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"version-5.0/docs",previous:{title:"Error handling",permalink:"/docs/5.0/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/5.0/authentication-authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],c={toc:p},m="wrapper";function h(e){let{components:a,...t}=e;return(0,r.yg)(m,(0,n.A)({},c,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,r.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,r.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,r.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,r.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,r.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,r.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,r.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,r.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,r.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,r.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,r.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,r.yg)("p",null,"If the data entered by the user is ",(0,r.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,r.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\GraphQLite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,r.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,r.yg)("p",null,"You can also pass an array to the ",(0,r.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,r.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1950],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>V});var n=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),d=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function c(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??p(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function m(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:t}=e;const n=(0,l.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function y(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!m({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=h({queryString:t,groupId:n}),[p,y]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,d.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),g=(()=>{const e=s??p;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,i]),tabValues:i}}var g=t(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:a,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),c=e=>{const a=e.currentTarget,t=d.indexOf(a),n=u[t].value;n!==l&&(p(a),s(n))},m=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const t=d.indexOf(e.currentTarget)+1;a=d[t]??d[0];break}case"ArrowLeft":{const t=d.indexOf(e.currentTarget)-1;a=d[t]??d[d.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===a?0:-1,"aria-selected":l===a,key:a,ref:e=>d.push(e),onKeyDown:m,onClick:c},o,{className:(0,i.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=y(e);return r.createElement("div",{className:(0,i.A)("tabs-container",v.tabList)},r.createElement(f,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function V(e){const a=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},2192:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-5.0/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-5.0/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/5.0/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/validation.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"version-5.0/docs",previous:{title:"Error handling",permalink:"/docs/5.0/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/5.0/authentication-authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],c={toc:p},m="wrapper";function h(e){let{components:a,...t}=e;return(0,r.yg)(m,(0,n.A)({},c,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,r.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,r.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,r.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,r.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,r.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,r.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,r.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,r.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,r.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,r.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,r.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,r.yg)("p",null,"If the data entered by the user is ",(0,r.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,r.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\GraphQLite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,r.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,r.yg)("p",null,"You can also pass an array to the ",(0,r.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,r.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/23f642f2.30bbac0d.js b/assets/js/23f642f2.3e877c32.js similarity index 99% rename from assets/js/23f642f2.30bbac0d.js rename to assets/js/23f642f2.3e877c32.js index 6dc130bb45..15f21c80e0 100644 --- a/assets/js/23f642f2.30bbac0d.js +++ b/assets/js/23f642f2.3e877c32.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2964],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),r=n(96540),o=n(20053),l=n(23104),u=n(56347),i=n(57485),p=n(31682),s=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=d(e),[l,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[i,p]=h({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,s.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),m=(()=>{const e=i??c;return y({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{m&&u(m)}),[m]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);u(e),p(e),g(e)}),[p,g,o]),tabValues:o}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:u,selectValue:i,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:c}=(0,l.a_)(),d=e=>{const t=e.currentTarget,n=s.indexOf(t),a=p[n].value;a!==u&&(c(t),i(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=s.indexOf(e.currentTarget)+1;t=s[n]??s[0];break}case"ArrowLeft":{const n=s.indexOf(e.currentTarget)-1;t=s[n]??s[s.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:d},l,{className:(0,o.A)("tabs__item",f.tabItem,l?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function P(e){const t=g(e);return r.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function T(e){const t=(0,m.A)();return r.createElement(P,(0,a.A)({key:String(t)},e))}},64417:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>p,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),o=(n(67443),n(11470)),l=n(19365);const u={id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},i=void 0,p={unversionedId:"input-types",id:"version-4.1/input-types",title:"Input types",description:"Let's admit you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-4.1/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/4.1/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/input-types.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},sidebar:"version-4.1/docs",previous:{title:"External type declaration",permalink:"/docs/4.1/external_type_declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/4.1/inheritance-interfaces"}},s={},c=[{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3}],d={toc:c},y="wrapper";function h(e){let{components:t,...n}=e;return(0,r.yg)(y,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Let's admit you are developing an API that returns a list of cities around a location."),(0,r.yg)("p",null,"Your GraphQL query might look like this:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,r.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,r.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,r.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,r.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,r.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,r.yg)("p",null,"In order to declare that type, in GraphQLite, we will declare a ",(0,r.yg)("strong",{parentName:"p"},"Factory"),"."),(0,r.yg)("p",null,"A ",(0,r.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,r.yg)("p",null,"Here is an example of factory:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")))),(0,r.yg)("p",null,"and now, you can run query like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"mutation {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,r.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,r.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,r.yg)("p",null,"A few important things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,r.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,r.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,r.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,r.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")))),(0,r.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')))),(0,r.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,r.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,r.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,r.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')))),(0,r.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,r.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,r.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,r.yg)("p",null,"Here is an annotated sample:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,r.yg)("p",null,"Image your ",(0,r.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,r.yg)("p",null,"To be able to hide an argument, the argument must have a default value."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2964],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),r=n(96540),o=n(20053),l=n(23104),u=n(56347),i=n(57485),p=n(31682),s=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=d(e),[l,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[i,p]=h({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,s.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),m=(()=>{const e=i??c;return y({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{m&&u(m)}),[m]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);u(e),p(e),g(e)}),[p,g,o]),tabValues:o}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:u,selectValue:i,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:c}=(0,l.a_)(),d=e=>{const t=e.currentTarget,n=s.indexOf(t),a=p[n].value;a!==u&&(c(t),i(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=s.indexOf(e.currentTarget)+1;t=s[n]??s[0];break}case"ArrowLeft":{const n=s.indexOf(e.currentTarget)-1;t=s[n]??s[s.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:d},l,{className:(0,o.A)("tabs__item",f.tabItem,l?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function P(e){const t=g(e);return r.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function T(e){const t=(0,m.A)();return r.createElement(P,(0,a.A)({key:String(t)},e))}},64417:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>p,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),o=(n(67443),n(11470)),l=n(19365);const u={id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},i=void 0,p={unversionedId:"input-types",id:"version-4.1/input-types",title:"Input types",description:"Let's admit you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-4.1/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/4.1/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/input-types.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},sidebar:"version-4.1/docs",previous:{title:"External type declaration",permalink:"/docs/4.1/external_type_declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/4.1/inheritance-interfaces"}},s={},c=[{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3}],d={toc:c},y="wrapper";function h(e){let{components:t,...n}=e;return(0,r.yg)(y,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Let's admit you are developing an API that returns a list of cities around a location."),(0,r.yg)("p",null,"Your GraphQL query might look like this:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,r.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,r.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,r.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,r.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,r.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,r.yg)("p",null,"In order to declare that type, in GraphQLite, we will declare a ",(0,r.yg)("strong",{parentName:"p"},"Factory"),"."),(0,r.yg)("p",null,"A ",(0,r.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,r.yg)("p",null,"Here is an example of factory:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")))),(0,r.yg)("p",null,"and now, you can run query like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"mutation {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,r.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,r.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,r.yg)("p",null,"A few important things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,r.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,r.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,r.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,r.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")))),(0,r.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')))),(0,r.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,r.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,r.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,r.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')))),(0,r.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,r.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,r.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,r.yg)("p",null,"Here is an annotated sample:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,r.yg)("p",null,"Image your ",(0,r.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,r.yg)("p",null,"To be able to hide an argument, the argument must have a default value."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/242d99d9.589df066.js b/assets/js/242d99d9.86bda269.js similarity index 89% rename from assets/js/242d99d9.589df066.js rename to assets/js/242d99d9.86bda269.js index d206758136..e2ba37f406 100644 --- a/assets/js/242d99d9.589df066.js +++ b/assets/js/242d99d9.86bda269.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3088],{92522:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting",original_id:"troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-4.0/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-4.0/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/4.0/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/troubleshooting.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting",original_id:"troubleshooting"},sidebar:"version-4.0/docs",previous:{title:"Internals",permalink:"/docs/4.0/internals"},next:{title:"Migrating",permalink:"/docs/4.0/migrating"}},l={},u=[],g={toc:u},p="wrapper";function d(e){let{components:t,...o}=e;return(0,r.yg)(p,(0,n.A)({},g,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3088],{92522:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>c,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting",original_id:"troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-4.0/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-4.0/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/4.0/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/troubleshooting.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting",original_id:"troubleshooting"},sidebar:"version-4.0/docs",previous:{title:"Internals",permalink:"/docs/4.0/internals"},next:{title:"Migrating",permalink:"/docs/4.0/migrating"}},l={},u=[],g={toc:u},p="wrapper";function c(e){let{components:t,...o}=e;return(0,r.yg)(p,(0,n.A)({},g,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/24ac61c7.cd8e555e.js b/assets/js/24ac61c7.52eb9adb.js similarity index 96% rename from assets/js/24ac61c7.cd8e555e.js rename to assets/js/24ac61c7.52eb9adb.js index caa96ffbfe..247846d684 100644 --- a/assets/js/24ac61c7.cd8e555e.js +++ b/assets/js/24ac61c7.52eb9adb.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9008],{35313:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>o,contentTitle:()=>s,default:()=>h,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"},s=void 0,l={unversionedId:"inheritance",id:"version-3.0/inheritance",title:"Inheritance and interfaces",description:"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces.",source:"@site/versioned_docs/version-3.0/inheritance.md",sourceDirName:".",slug:"/inheritance",permalink:"/docs/3.0/inheritance",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/inheritance.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"},sidebar:"version-3.0/docs",previous:{title:"Input types",permalink:"/docs/3.0/input-types"},next:{title:"File uploads",permalink:"/docs/3.0/file-uploads"}},o={},c=[],p={toc:c},d="wrapper";function h(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types would look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("div",{class:"alert alert--warning"},"Right now, there is no way to explicitly declare a GraphQL interface using GraphQLite.",(0,i.yg)("br",null),"GraphQLite automatically declares interfaces when it sees an inheritance relationship between to classes that are GraphQL types."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9008],{35313:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>o,contentTitle:()=>s,default:()=>h,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"},s=void 0,l={unversionedId:"inheritance",id:"version-3.0/inheritance",title:"Inheritance and interfaces",description:"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces.",source:"@site/versioned_docs/version-3.0/inheritance.md",sourceDirName:".",slug:"/inheritance",permalink:"/docs/3.0/inheritance",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/inheritance.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"},sidebar:"version-3.0/docs",previous:{title:"Input types",permalink:"/docs/3.0/input-types"},next:{title:"File uploads",permalink:"/docs/3.0/file-uploads"}},o={},c=[],p={toc:c},d="wrapper";function h(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types would look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("div",{class:"alert alert--warning"},"Right now, there is no way to explicitly declare a GraphQL interface using GraphQLite.",(0,i.yg)("br",null),"GraphQLite automatically declares interfaces when it sees an inheritance relationship between to classes that are GraphQL types."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/24aca886.954f6b8f.js b/assets/js/24aca886.82ecee1c.js similarity index 96% rename from assets/js/24aca886.954f6b8f.js rename to assets/js/24aca886.82ecee1c.js index 8190f8fa18..968ad53b67 100644 --- a/assets/js/24aca886.954f6b8f.js +++ b/assets/js/24aca886.82ecee1c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3820],{6874:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals",original_id:"internals"},s=void 0,o={unversionedId:"internals",id:"version-4.0/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-4.0/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/4.0/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/internals.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals",original_id:"internals"},sidebar:"version-4.0/docs",previous:{title:"Laravel specific features",permalink:"/docs/4.0/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/4.0/troubleshooting"}},l={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.0/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.0/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3820],{6874:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>o,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals",original_id:"internals"},s=void 0,l={unversionedId:"internals",id:"version-4.0/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-4.0/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/4.0/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/internals.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals",original_id:"internals"},sidebar:"version-4.0/docs",previous:{title:"Laravel specific features",permalink:"/docs/4.0/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/4.0/troubleshooting"}},o={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.0/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.0/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/25d4129e.4553cb4f.js b/assets/js/25d4129e.6d8bd7b7.js similarity index 99% rename from assets/js/25d4129e.4553cb4f.js rename to assets/js/25d4129e.6d8bd7b7.js index 2f9ce4c657..392b8c2576 100644 --- a/assets/js/25d4129e.4553cb4f.js +++ b/assets/js/25d4129e.6d8bd7b7.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2459],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},46065:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-7.0.0/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-7.0.0/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/error-handling.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2459],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},46065:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-7.0.0/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-7.0.0/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/error-handling.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/263ebc7a.2e214490.js b/assets/js/263ebc7a.c6dc1aef.js similarity index 98% rename from assets/js/263ebc7a.2e214490.js rename to assets/js/263ebc7a.c6dc1aef.js index efbf255ed7..1fdee28762 100644 --- a/assets/js/263ebc7a.2e214490.js +++ b/assets/js/263ebc7a.c6dc1aef.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3642],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),i=n(20053),o=n(23104),u=n(56347),s=n(57485),l=n(31682),c=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,l.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function g(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function p(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=h(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[s,l]=p({queryString:n,groupId:a}),[d,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),y=(()=>{const e=s??d;return g({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);u(e),l(e),m(e)}),[l,m,i]),tabValues:i}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:l}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=l[n].value;a!==u&&(d(t),s(a))},g=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},l.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:h},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function N(e){const t=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(t)},e))}},55123:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>h,frontMatter:()=>i,metadata:()=>u,toc:()=>l});var a=n(58168),r=(n(96540),n(15680));n(67443),n(11470),n(19365);const i={id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},o=void 0,u={unversionedId:"authentication-authorization",id:"version-6.1/authentication-authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-6.1/authentication-authorization.mdx",sourceDirName:".",slug:"/authentication-authorization",permalink:"/docs/6.1/authentication-authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/authentication-authorization.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},sidebar:"docs",previous:{title:"User input validation",permalink:"/docs/6.1/validation"},next:{title:"Fine grained security",permalink:"/docs/6.1/fine-grained-security"}},s={},l=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],c={toc:l},d="wrapper";function h(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/6.1/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3642],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),i=n(20053),o=n(23104),u=n(56347),s=n(57485),l=n(31682),c=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,l.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function g(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function p(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=h(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[s,l]=p({queryString:n,groupId:a}),[d,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),y=(()=>{const e=s??d;return g({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);u(e),l(e),m(e)}),[l,m,i]),tabValues:i}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:l}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=l[n].value;a!==u&&(d(t),s(a))},g=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},l.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:h},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function N(e){const t=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(t)},e))}},55123:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>h,frontMatter:()=>i,metadata:()=>u,toc:()=>l});var a=n(58168),r=(n(96540),n(15680));n(67443),n(11470),n(19365);const i={id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},o=void 0,u={unversionedId:"authentication-authorization",id:"version-6.1/authentication-authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-6.1/authentication-authorization.mdx",sourceDirName:".",slug:"/authentication-authorization",permalink:"/docs/6.1/authentication-authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/authentication-authorization.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},sidebar:"docs",previous:{title:"User input validation",permalink:"/docs/6.1/validation"},next:{title:"Fine grained security",permalink:"/docs/6.1/fine-grained-security"}},s={},l=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],c={toc:l},d="wrapper";function h(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/6.1/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/26662da3.26149760.js b/assets/js/26662da3.da5a7b07.js similarity index 96% rename from assets/js/26662da3.26149760.js rename to assets/js/26662da3.da5a7b07.js index 009ed06236..518cf49fc7 100644 --- a/assets/js/26662da3.26149760.js +++ b/assets/js/26662da3.da5a7b07.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7321],{16591:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,o={unversionedId:"internals",id:"version-3.0/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-3.0/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/3.0/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/internals.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"}},l={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/3.0/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7321],{16591:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>o,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,l={unversionedId:"internals",id:"version-3.0/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-3.0/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/3.0/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/internals.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"}},o={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/3.0/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/26a27afb.fd1bb7ed.js b/assets/js/26a27afb.acfabf90.js similarity index 98% rename from assets/js/26a27afb.fd1bb7ed.js rename to assets/js/26a27afb.acfabf90.js index dce3facfc5..f16f74be8c 100644 --- a/assets/js/26a27afb.fd1bb7ed.js +++ b/assets/js/26a27afb.acfabf90.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4954],{1656:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-4.2/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.2/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/4.2/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/symfony-bundle.md",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"version-4.2/docs",previous:{title:"Getting Started",permalink:"/docs/4.2/getting-started"},next:{title:"Laravel package",permalink:"/docs/4.2/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-4.2/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.2/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/4.2/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/symfony-bundle.md",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"version-4.2/docs",previous:{title:"Getting Started",permalink:"/docs/4.2/getting-started"},next:{title:"Laravel package",permalink:"/docs/4.2/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>y,toc:()=>i});var n=a(58168),p=(a(96540),a(15680));a(67443);const r={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},o=void 0,y={unversionedId:"custom-types",id:"custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/docs/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/next/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/custom-types.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"docs",previous:{title:"Pagination",permalink:"/docs/next/pagination"},next:{title:"Custom attributes",permalink:"/docs/next/field-middlewares"}},l={},i=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],s={toc:i},u="wrapper";function c(e){let{components:t,...a}=e;return(0,p.yg)(u,(0,n.A)({},s,a,{components:t,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")),(0,p.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,p.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,p.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,p.yg)("p",null,"GraphQL comes with an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,p.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n')),(0,p.yg)("h2",{id:"usage"},"Usage"),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,p.yg)("p",null,"You can use the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[Query]")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[Mutation]")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[Subscription]")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[Field]")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[SourceField]")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[MagicField]"))),(0,p.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,p.yg)("p",null,"In order to create a custom output type, you need to:"),(0,p.yg)("ol",null,(0,p.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,p.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,p.yg)("p",null,"You'll find more details on the ",(0,p.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,p.yg)("hr",null),(0,p.yg)("p",null,"In order to find existing types, the schema is using ",(0,p.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,p.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,p.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,p.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,p.yg)("p",null,"Any class extending ",(0,p.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,p.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,p.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,p.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,p.yg)("p",null,"The easiest way is to use a ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper(\n // Let's register a type that maps by default to the \"MyClass\" PHP class\n types: [\n MyClass::class => new MyCustomOutputType()\n ],\n\n // If you don't want your output type to map to any PHP class by default, use:\n notMappedTypes: [\n new MyCustomOutputType()\n ],\n);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,p.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,p.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,p.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,p.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},"create a ",(0,p.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,p.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,p.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,p.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,p.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,p.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,p.yg)("p",null,"RootTypeMapper are organized ",(0,p.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,p.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,p.yg)("p",null,"Now, in order to create an instance of your ",(0,p.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,p.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,p.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,p.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,p.yg)("p",null,"You can register your own root mapper factories using the ",(0,p.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,p.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1985],{1273:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>y,toc:()=>i});var n=a(58168),p=(a(96540),a(15680));a(67443);const r={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},o=void 0,y={unversionedId:"custom-types",id:"custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/docs/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/next/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/custom-types.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"docs",previous:{title:"Pagination",permalink:"/docs/next/pagination"},next:{title:"Custom attributes",permalink:"/docs/next/field-middlewares"}},l={},i=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],s={toc:i},u="wrapper";function c(e){let{components:t,...a}=e;return(0,p.yg)(u,(0,n.A)({},s,a,{components:t,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")),(0,p.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,p.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,p.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,p.yg)("p",null,"GraphQL comes with an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,p.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n')),(0,p.yg)("h2",{id:"usage"},"Usage"),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,p.yg)("p",null,"You can use the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[Query]")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[Mutation]")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[Subscription]")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[Field]")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[SourceField]")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"#[MagicField]"))),(0,p.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,p.yg)("p",null,"In order to create a custom output type, you need to:"),(0,p.yg)("ol",null,(0,p.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,p.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,p.yg)("p",null,"You'll find more details on the ",(0,p.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,p.yg)("hr",null),(0,p.yg)("p",null,"In order to find existing types, the schema is using ",(0,p.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,p.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,p.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,p.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,p.yg)("p",null,"Any class extending ",(0,p.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,p.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,p.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,p.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,p.yg)("p",null,"The easiest way is to use a ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper(\n // Let's register a type that maps by default to the \"MyClass\" PHP class\n types: [\n MyClass::class => new MyCustomOutputType()\n ],\n\n // If you don't want your output type to map to any PHP class by default, use:\n notMappedTypes: [\n new MyCustomOutputType()\n ],\n);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,p.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,p.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,p.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,p.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},"create a ",(0,p.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,p.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,p.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,p.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,p.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,p.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,p.yg)("p",null,"RootTypeMapper are organized ",(0,p.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,p.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,p.yg)("p",null,"Now, in order to create an instance of your ",(0,p.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,p.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,p.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,p.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,p.yg)("p",null,"You can register your own root mapper factories using the ",(0,p.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,p.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/27b414e3.e46c4fea.js b/assets/js/27b414e3.549f2a3c.js similarity index 99% rename from assets/js/27b414e3.e46c4fea.js rename to assets/js/27b414e3.549f2a3c.js index 049ecda161..df91b60cef 100644 --- a/assets/js/27b414e3.e46c4fea.js +++ b/assets/js/27b414e3.549f2a3c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5923],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var r=a(96540),t=a(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>$});var r=a(58168),t=a(96540),o=a(20053),i=a(23104),l=a(56347),s=a(57485),c=a(31682),p=a(89466);function u(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:r,default:t}}=e;return{value:n,label:a,attributes:r,default:t}}))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function h(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:a}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,c]=m({queryString:a,groupId:r}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,p.Dv)(a);return[r,(0,t.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:r}),g=(()=>{const e=s??u;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),c(e),y(e)}),[c,y,o]),tabValues:o}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:s,tabValues:c}=e;const p=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),d=e=>{const n=e.currentTarget,a=p.indexOf(n),r=c[a].value;r!==l&&(u(n),s(r))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;n=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;n=p[a]??p[p.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return t.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>p.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),a??n)})))}function w(e){let{lazy:n,children:a,selectedValue:r}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,r.A)({},e,n)),t.createElement(w,(0,r.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,r.A)({key:String(n)},e))}},74665:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>c,toc:()=>u});var r=a(58168),t=(a(96540),a(15680)),o=(a(67443),a(11470)),i=a(19365);const l={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},s=void 0,c={unversionedId:"other-frameworks",id:"version-7.0.0/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-7.0.0/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/other-frameworks.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"docs",previous:{title:"Universal service providers",permalink:"/docs/universal-service-providers"},next:{title:"Queries",permalink:"/docs/queries"}},p={},u=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],d={toc:u},h="wrapper";function m(e){let{components:n,...a}=e;return(0,t.yg)(h,(0,r.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers')\n ->addTypeNamespace('App');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Set a default InputType validator service to handle validation on all `Input` annotated types\n$factory->setInputTypeValidator($validator);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers')\n ->addTypeNamespace('App');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"$builder->setUrl(\"/graphql\"); // Modify the URL endpoint (defaults to /graphql)\n\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object.\n// Define your own formatter and error handlers for Webonyx.\n$config->setErrorFormatter([ExceptionHandler::class, 'errorFormatter']);\n$config->setErrorsHandler([ExceptionHandler::class, 'errorHandler']);\n\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n\n// Configure the server to use Apollo automatic persisted queries with given cache and an optional time-to-live.\n// See https://www.apollographql.com/docs/apollo-server/performance/apq/\n$builder->useAutomaticPersistedQueries($cache, new DateInterval('PT1H'));\n")),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.laminas.dev/laminas-stratigility/"},"Laminas Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "laminas/laminas-diactoros": "^2",\n "laminas/laminas-stratigility": "^3",\n "laminas/laminas-httphandlerrunner": "^2",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Laminas ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Laminas Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers');\n $factory->addTypeNamespace('App');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,t.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n"))),(0,t.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")))),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5923],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var r=a(96540),t=a(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>$});var r=a(58168),t=a(96540),o=a(20053),i=a(23104),l=a(56347),s=a(57485),c=a(31682),p=a(89466);function u(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:r,default:t}}=e;return{value:n,label:a,attributes:r,default:t}}))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function h(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:a}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,c]=m({queryString:a,groupId:r}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,p.Dv)(a);return[r,(0,t.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:r}),g=(()=>{const e=s??u;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),c(e),y(e)}),[c,y,o]),tabValues:o}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:s,tabValues:c}=e;const p=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),d=e=>{const n=e.currentTarget,a=p.indexOf(n),r=c[a].value;r!==l&&(u(n),s(r))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;n=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;n=p[a]??p[p.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return t.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>p.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),a??n)})))}function w(e){let{lazy:n,children:a,selectedValue:r}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,r.A)({},e,n)),t.createElement(w,(0,r.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,r.A)({key:String(n)},e))}},74665:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>c,toc:()=>u});var r=a(58168),t=(a(96540),a(15680)),o=(a(67443),a(11470)),i=a(19365);const l={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},s=void 0,c={unversionedId:"other-frameworks",id:"version-7.0.0/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-7.0.0/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/other-frameworks.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"docs",previous:{title:"Universal service providers",permalink:"/docs/universal-service-providers"},next:{title:"Queries",permalink:"/docs/queries"}},p={},u=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],d={toc:u},h="wrapper";function m(e){let{components:n,...a}=e;return(0,t.yg)(h,(0,r.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers')\n ->addTypeNamespace('App');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Set a default InputType validator service to handle validation on all `Input` annotated types\n$factory->setInputTypeValidator($validator);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers')\n ->addTypeNamespace('App');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"$builder->setUrl(\"/graphql\"); // Modify the URL endpoint (defaults to /graphql)\n\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object.\n// Define your own formatter and error handlers for Webonyx.\n$config->setErrorFormatter([ExceptionHandler::class, 'errorFormatter']);\n$config->setErrorsHandler([ExceptionHandler::class, 'errorHandler']);\n\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n\n// Configure the server to use Apollo automatic persisted queries with given cache and an optional time-to-live.\n// See https://www.apollographql.com/docs/apollo-server/performance/apq/\n$builder->useAutomaticPersistedQueries($cache, new DateInterval('PT1H'));\n")),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.laminas.dev/laminas-stratigility/"},"Laminas Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "laminas/laminas-diactoros": "^2",\n "laminas/laminas-stratigility": "^3",\n "laminas/laminas-httphandlerrunner": "^2",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Laminas ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Laminas Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers');\n $factory->addTypeNamespace('App');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,t.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n"))),(0,t.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")))),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/28c12eaf.e0ba58bd.js b/assets/js/28c12eaf.77b5e230.js similarity index 99% rename from assets/js/28c12eaf.e0ba58bd.js rename to assets/js/28c12eaf.77b5e230.js index 04f3c8b3e1..eb083129c1 100644 --- a/assets/js/28c12eaf.e0ba58bd.js +++ b/assets/js/28c12eaf.77b5e230.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6931],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>v});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function w(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function v(e){const n=(0,m.A)();return a.createElement(w,(0,r.A)({key:String(n)},e))}},93223:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>h,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var r=t(58168),a=(t(96540),t(15680));t(67443),t(11470),t(19365);const o={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},i=void 0,l={unversionedId:"error-handling",id:"version-6.1/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-6.1/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/6.1/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/error-handling.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/6.1/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/6.1/validation"}},s={},u=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],c={toc:u},p="wrapper";function h(e){let{components:n,...t}=e;return(0,a.yg)(p,(0,r.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/6.1/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/6.1/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6931],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>v});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function w(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function v(e){const n=(0,m.A)();return a.createElement(w,(0,r.A)({key:String(n)},e))}},93223:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>h,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var r=t(58168),a=(t(96540),t(15680));t(67443),t(11470),t(19365);const o={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},i=void 0,l={unversionedId:"error-handling",id:"version-6.1/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-6.1/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/6.1/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/error-handling.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/6.1/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/6.1/validation"}},s={},u=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],c={toc:u},p="wrapper";function h(e){let{components:n,...t}=e;return(0,a.yg)(p,(0,r.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/6.1/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/6.1/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2917b31e.d68b38c9.js b/assets/js/2917b31e.e312ae9c.js similarity index 99% rename from assets/js/2917b31e.d68b38c9.js rename to assets/js/2917b31e.e312ae9c.js index 7c91967ccd..74d59cf26b 100644 --- a/assets/js/2917b31e.d68b38c9.js +++ b/assets/js/2917b31e.e312ae9c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8735],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),u=a(57485),s=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[u,s]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=u??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),h(e)}),[s,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:u,tabValues:s}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=s[a].value;n!==l&&(y(t),u(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},65352:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>u,default:()=>m,frontMatter:()=>l,metadata:()=>s,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},u=void 0,s={unversionedId:"custom-types",id:"version-7.0.0/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-7.0.0/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/custom-types.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"docs",previous:{title:"Pagination",permalink:"/docs/pagination"},next:{title:"Custom annotations",permalink:"/docs/field-middlewares"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Subscription")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper(\n // Let's register a type that maps by default to the \"MyClass\" PHP class\n types: [\n MyClass::class => new MyCustomOutputType()\n ],\n\n // If you don't want your output type to map to any PHP class by default, use:\n notMappedTypes: [\n new MyCustomOutputType()\n ],\n);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8735],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),u=a(57485),s=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[u,s]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=u??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),h(e)}),[s,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:u,tabValues:s}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=s[a].value;n!==l&&(y(t),u(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},65352:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>u,default:()=>m,frontMatter:()=>l,metadata:()=>s,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},u=void 0,s={unversionedId:"custom-types",id:"version-7.0.0/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-7.0.0/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/custom-types.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"docs",previous:{title:"Pagination",permalink:"/docs/pagination"},next:{title:"Custom annotations",permalink:"/docs/field-middlewares"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Subscription")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper(\n // Let's register a type that maps by default to the \"MyClass\" PHP class\n types: [\n MyClass::class => new MyCustomOutputType()\n ],\n\n // If you don't want your output type to map to any PHP class by default, use:\n notMappedTypes: [\n new MyCustomOutputType()\n ],\n);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/29a6c1ba.6de15e74.js b/assets/js/29a6c1ba.80f0a1f0.js similarity index 99% rename from assets/js/29a6c1ba.6de15e74.js rename to assets/js/29a6c1ba.80f0a1f0.js index faea52ec02..f400c2c9b4 100644 --- a/assets/js/29a6c1ba.6de15e74.js +++ b/assets/js/29a6c1ba.80f0a1f0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8042],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),o=a(57485),p=a(31682),s=a(89466);function d(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function c(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,o.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=c(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[o,p]=m({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=o??d;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),p(e),h(e)}),[p,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:o,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=s.indexOf(t),n=p[a].value;n!==i&&(d(t),o(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},p.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:c},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},28376:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>i,metadata:()=>p,toc:()=>d});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},o=void 0,p={unversionedId:"multiple-output-types",id:"version-4.2/multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.2/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/4.2/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/multiple-output-types.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"version-4.2/docs",previous:{title:"Extending an input type",permalink:"/docs/4.2/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/4.2/symfony-bundle-advanced"}},s={},d=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],c={toc:d},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/external-type-declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8042],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),o=a(57485),p=a(31682),s=a(89466);function d(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function c(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,o.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=c(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[o,p]=m({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=o??d;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),p(e),h(e)}),[p,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:o,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=s.indexOf(t),n=p[a].value;n!==i&&(d(t),o(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},p.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:c},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},28376:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>i,metadata:()=>p,toc:()=>d});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},o=void 0,p={unversionedId:"multiple-output-types",id:"version-4.2/multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.2/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/4.2/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/multiple-output-types.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"version-4.2/docs",previous:{title:"Extending an input type",permalink:"/docs/4.2/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/4.2/symfony-bundle-advanced"}},s={},d=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],c={toc:d},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/external-type-declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/29cf2ad6.20b4bd04.js b/assets/js/29cf2ad6.30734bf9.js similarity index 98% rename from assets/js/29cf2ad6.20b4bd04.js rename to assets/js/29cf2ad6.30734bf9.js index 2dcbd786fc..2f8ce7038d 100644 --- a/assets/js/29cf2ad6.20b4bd04.js +++ b/assets/js/29cf2ad6.30734bf9.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2858],{6661:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>r,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>u,toc:()=>s});var a=t(58168),i=(t(96540),t(15680));t(67443);const l={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},o=void 0,u={unversionedId:"symfony-bundle-advanced",id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/docs/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/next/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/symfony-bundle-advanced.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"docs",previous:{title:"Class with multiple output types",permalink:"/docs/next/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/next/laravel-package-advanced"}},r={},s=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],g={toc:s},y="wrapper";function d(e){let{components:n,...t}=e;return(0,i.yg)(y,(0,a.A)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,i.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,i.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,i.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,i.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,i.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,i.yg)("p",null,"By settings ",(0,i.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,i.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,i.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,i.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,i.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,i.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,i.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,i.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,i.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,i.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,i.yg)("p",null,"The mutation below will log-in a user:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,i.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,i.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,i.yg)("p",null,"In Symfony, user objects implement ",(0,i.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,i.yg)("p",null,"If you want to get more fields, just add the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute to your user class:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")),(0,i.yg)("p",null,"You can now query this field using an ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,i.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,i.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,i.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,i.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,i.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2858],{6661:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>r,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>u,toc:()=>s});var a=t(58168),i=(t(96540),t(15680));t(67443);const l={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},o=void 0,u={unversionedId:"symfony-bundle-advanced",id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/docs/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/next/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/symfony-bundle-advanced.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"docs",previous:{title:"Class with multiple output types",permalink:"/docs/next/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/next/laravel-package-advanced"}},r={},s=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],g={toc:s},y="wrapper";function d(e){let{components:n,...t}=e;return(0,i.yg)(y,(0,a.A)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,i.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,i.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,i.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,i.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,i.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,i.yg)("p",null,"By settings ",(0,i.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,i.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,i.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,i.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,i.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,i.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,i.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,i.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,i.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,i.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,i.yg)("p",null,"The mutation below will log-in a user:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,i.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,i.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,i.yg)("p",null,"In Symfony, user objects implement ",(0,i.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,i.yg)("p",null,"If you want to get more fields, just add the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute to your user class:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")),(0,i.yg)("p",null,"You can now query this field using an ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,i.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,i.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,i.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,i.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,i.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2b26025e.fc3ba436.js b/assets/js/2b26025e.e6842bcd.js similarity index 60% rename from assets/js/2b26025e.fc3ba436.js rename to assets/js/2b26025e.e6842bcd.js index 2d2574c38a..aff1ffe2b2 100644 --- a/assets/js/2b26025e.fc3ba436.js +++ b/assets/js/2b26025e.e6842bcd.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9127],{9025:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>c,frontMatter:()=>i,metadata:()=>o,toc:()=>d});var n=a(58168),r=(a(96540),a(15680));a(67443);const i={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},l=void 0,o={unversionedId:"index",id:"version-6.1/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-6.1/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/6.1/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/README.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"docs",next:{title:"Getting Started",permalink:"/docs/6.1/getting-started"}},s={},d=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],p={toc:d},u="wrapper";function c(e){let{components:t,...a}=e;return(0,r.yg)(u,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9127],{9025:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>c,frontMatter:()=>i,metadata:()=>o,toc:()=>d});var r=a(58168),n=(a(96540),a(15680));a(67443);const i={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},l=void 0,o={unversionedId:"index",id:"version-6.1/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-6.1/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/6.1/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/README.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"docs",next:{title:"Getting Started",permalink:"/docs/6.1/getting-started"}},s={},d=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],p={toc:d},u="wrapper";function c(e){let{components:t,...a}=e;return(0,n.yg)(u,(0,r.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,n.yg)("p",{align:"center"},(0,n.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,n.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,n.yg)("h2",{id:"features"},"Features"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,n.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,n.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,n.yg)("h2",{id:"basic-example"},"Basic example"),(0,n.yg)("p",null,"First, declare a query in your controller:"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,n.yg)("p",null,"Then, annotate the ",(0,n.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")),(0,n.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2bbfc5d5.21599b38.js b/assets/js/2bbfc5d5.5e6bcac0.js similarity index 91% rename from assets/js/2bbfc5d5.21599b38.js rename to assets/js/2bbfc5d5.5e6bcac0.js index 7440a9452d..dbccb5d263 100644 --- a/assets/js/2bbfc5d5.21599b38.js +++ b/assets/js/2bbfc5d5.5e6bcac0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[458],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),o=a(57485),p=a(31682),s=a(89466);function d(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function c(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,o.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=c(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[o,p]=m({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=o??d;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),p(e),h(e)}),[p,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:o,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=s.indexOf(t),n=p[a].value;n!==i&&(d(t),o(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},p.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:c},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},35529:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>i,metadata:()=>p,toc:()=>d});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},o=void 0,p={unversionedId:"multiple_output_types",id:"version-3.0/multiple_output_types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-3.0/multiple_output_types.mdx",sourceDirName:".",slug:"/multiple_output_types",permalink:"/docs/3.0/multiple_output_types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/multiple_output_types.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"}},s={},d=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],c={toc:d},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/3.0/external_type_declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[458],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),o=a(57485),p=a(31682),s=a(89466);function c(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function d(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,o.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=d(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[o,p]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=o??c;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),p(e),h(e)}),[p,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:o,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:c}=(0,r.a_)(),d=e=>{const t=e.currentTarget,a=s.indexOf(t),n=p[a].value;n!==i&&(c(t),o(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},p.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:d},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},35529:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>i,metadata:()=>p,toc:()=>c});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},o=void 0,p={unversionedId:"multiple_output_types",id:"version-3.0/multiple_output_types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-3.0/multiple_output_types.mdx",sourceDirName:".",slug:"/multiple_output_types",permalink:"/docs/3.0/multiple_output_types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/multiple_output_types.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"}},s={},c=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/3.0/external_type_declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2d02c83c.ff4ec7ba.js b/assets/js/2d02c83c.abba1f2c.js similarity index 98% rename from assets/js/2d02c83c.ff4ec7ba.js rename to assets/js/2d02c83c.abba1f2c.js index e37c66197b..39fb3b6bed 100644 --- a/assets/js/2d02c83c.ff4ec7ba.js +++ b/assets/js/2d02c83c.abba1f2c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5e3],{60142:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var i=t(58168),a=(t(96540),t(15680));t(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},r=void 0,o={unversionedId:"field-middlewares",id:"version-6.0/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.0/field-middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/6.0/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/field-middlewares.md",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},sidebar:"docs",previous:{title:"Custom types",permalink:"/docs/6.0/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/6.0/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour of a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:t(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library (for PHP 7+) and/or by PHP 8 attributes."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="OnlyDebug.php"',title:'"OnlyDebug.php"'},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation is to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:"),(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"p"},"graphql.field_middleware")," tag."))))}c.isMDXComponent=!0},8643:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5e3],{60142:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var i=t(58168),a=(t(96540),t(15680));t(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},r=void 0,o={unversionedId:"field-middlewares",id:"version-6.0/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.0/field-middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/6.0/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/field-middlewares.md",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},sidebar:"docs",previous:{title:"Custom types",permalink:"/docs/6.0/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/6.0/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour of a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:t(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library (for PHP 7+) and/or by PHP 8 attributes."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="OnlyDebug.php"',title:'"OnlyDebug.php"'},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation is to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:"),(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"p"},"graphql.field_middleware")," tag."))))}c.isMDXComponent=!0},8643:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file diff --git a/assets/js/2d4548df.8a0eaadc.js b/assets/js/2d4548df.098efc22.js similarity index 98% rename from assets/js/2d4548df.8a0eaadc.js rename to assets/js/2d4548df.098efc22.js index ff0b91cbc9..eee18c2388 100644 --- a/assets/js/2d4548df.8a0eaadc.js +++ b/assets/js/2d4548df.098efc22.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8478],{37942:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>d,toc:()=>s});var i=n(58168),a=(n(96540),n(15680));n(67443);const l={id:"field-middlewares",title:"Adding custom attributes with Field middlewares",sidebar_label:"Custom attributes"},r=void 0,d={unversionedId:"field-middlewares",id:"field-middlewares",title:"Adding custom attributes with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/docs/field-middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/next/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/field-middlewares.md",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom attributes with Field middlewares",sidebar_label:"Custom attributes"},sidebar:"docs",previous:{title:"Custom types",permalink:"/docs/next/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/next/argument-resolving"}},o={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Attributes parsing",id:"attributes-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:t,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"#[Logged]")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"#[Right]")," attribute, you can develop your own attribute that extends/modifies the behaviour of a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an attribute that targets a single argument (like ",(0,a.yg)("code",null,"#[AutoWire]"),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:n(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function withName(string $name): self { /* ... */ }\n public function getType() { /* ... */ }\n public function withType($type): self { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function withParameters(array $parameters): self { /* ... */ }\n public function withCallable(callable $callable): self { /* ... */ }\n public function withTargetMethodOnSource(?string $targetMethodOnSource): self { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function withInjectSource(bool $injectSource): self { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function withComment(?string $comment): self { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function withMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): self { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function withResolver(callable $resolver): self { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),", although this should be used with caution:\nfield middlewares only get called once per Schema instance. If you use a long-running server (like Laravel Octane, Swoole, RoadRunner etc)\nand share the same Schema instance across requests, you will not be able to hide fields based on request data."),(0,a.yg)("h2",{id:"attributes-parsing"},"Attributes parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of attributes applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"#[OnlyDebug]")," attribute that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the attribute."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="OnlyDebug.php"',title:'"OnlyDebug.php"'},"namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n")),(0,a.yg)("p",null,"Apart from being a classical attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this attribute is to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this attribute."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with #[OnlyDebug] and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug attribute is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:"),(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"p"},"graphql.field_middleware")," tag."))))}c.isMDXComponent=!0},8643:(e,t,n)=>{n.d(t,{A:()=>i});const i=n.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8478],{37942:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>d,toc:()=>s});var i=n(58168),a=(n(96540),n(15680));n(67443);const l={id:"field-middlewares",title:"Adding custom attributes with Field middlewares",sidebar_label:"Custom attributes"},r=void 0,d={unversionedId:"field-middlewares",id:"field-middlewares",title:"Adding custom attributes with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/docs/field-middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/next/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/field-middlewares.md",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom attributes with Field middlewares",sidebar_label:"Custom attributes"},sidebar:"docs",previous:{title:"Custom types",permalink:"/docs/next/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/next/argument-resolving"}},o={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Attributes parsing",id:"attributes-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:t,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"#[Logged]")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"#[Right]")," attribute, you can develop your own attribute that extends/modifies the behaviour of a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an attribute that targets a single argument (like ",(0,a.yg)("code",null,"#[AutoWire]"),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:n(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function withName(string $name): self { /* ... */ }\n public function getType() { /* ... */ }\n public function withType($type): self { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function withParameters(array $parameters): self { /* ... */ }\n public function withCallable(callable $callable): self { /* ... */ }\n public function withTargetMethodOnSource(?string $targetMethodOnSource): self { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function withInjectSource(bool $injectSource): self { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function withComment(?string $comment): self { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function withMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): self { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function withResolver(callable $resolver): self { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),", although this should be used with caution:\nfield middlewares only get called once per Schema instance. If you use a long-running server (like Laravel Octane, Swoole, RoadRunner etc)\nand share the same Schema instance across requests, you will not be able to hide fields based on request data."),(0,a.yg)("h2",{id:"attributes-parsing"},"Attributes parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of attributes applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"#[OnlyDebug]")," attribute that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the attribute."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="OnlyDebug.php"',title:'"OnlyDebug.php"'},"namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n")),(0,a.yg)("p",null,"Apart from being a classical attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this attribute is to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this attribute."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with #[OnlyDebug] and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug attribute is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:"),(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"p"},"graphql.field_middleware")," tag."))))}c.isMDXComponent=!0},8643:(e,t,n)=>{n.d(t,{A:()=>i});const i=n.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file diff --git a/assets/js/2e25c87f.3d095e48.js b/assets/js/2e25c87f.a3639132.js similarity index 94% rename from assets/js/2e25c87f.3d095e48.js rename to assets/js/2e25c87f.a3639132.js index 9b258cf2bc..837083d010 100644 --- a/assets/js/2e25c87f.3d095e48.js +++ b/assets/js/2e25c87f.a3639132.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1345],{8608:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>i,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>s});var a=t(58168),l=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},i=void 0,o={unversionedId:"symfony-bundle",id:"symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/docs/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/next/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/symfony-bundle.md",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"docs",previous:{title:"Getting Started",permalink:"/docs/next/getting-started"},next:{title:"Laravel package",permalink:"/docs/next/laravel-package"}},p={},s=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:s},d="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,l.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,l.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,l.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,l.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,l.yg)("p",null,"Now, go to the ",(0,l.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,l.yg)("p",null,"More advanced parameters are detailed in the ",(0,l.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,l.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,l.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,l.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,l.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>r,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>s});var a=t(58168),l=(t(96540),t(15680));t(67443);const i={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},r=void 0,o={unversionedId:"symfony-bundle",id:"symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/docs/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/next/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/symfony-bundle.md",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"docs",previous:{title:"Getting Started",permalink:"/docs/next/getting-started"},next:{title:"Laravel package",permalink:"/docs/next/laravel-package"}},p={},s=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:s},g="wrapper";function u(e){let{components:n,...t}=e;return(0,l.yg)(g,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,l.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,l.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,l.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,l.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,l.yg)("p",null,"Now, go to the ",(0,l.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,l.yg)("p",null,"More advanced parameters are detailed in the ",(0,l.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,l.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,l.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,l.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,l.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>g});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,o={unversionedId:"annotations-reference",id:"version-4.3/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-4.3/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/4.3/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/annotations-reference.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"version-4.3/docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/4.3/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/4.3/semver"}},p={},g=[{value:"@Query annotation",id:"query-annotation",level:2},{value:"@Mutation annotation",id:"mutation-annotation",level:2},{value:"@Type annotation",id:"type-annotation",level:2},{value:"@ExtendType annotation",id:"extendtype-annotation",level:2},{value:"@Input annotation",id:"input-annotation",level:2},{value:"@Field annotation",id:"field-annotation",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"@Logged annotation",id:"logged-annotation",level:2},{value:"@Right annotation",id:"right-annotation",level:2},{value:"@FailWith annotation",id:"failwith-annotation",level:2},{value:"@HideIfUnauthorized annotation",id:"hideifunauthorized-annotation",level:2},{value:"@InjectUser annotation",id:"injectuser-annotation",level:2},{value:"@Security annotation",id:"security-annotation",level:2},{value:"@Factory annotation",id:"factory-annotation",level:2},{value:"@UseInputType annotation",id:"useinputtype-annotation",level:2},{value:"@Decorate annotation",id:"decorate-annotation",level:2},{value:"@Autowire annotation",id:"autowire-annotation",level:2},{value:"@HideParameter annotation",id:"hideparameter-annotation",level:2},{value:"@Validate annotation",id:"validate-annotation",level:2},{value:"@Assertion annotation",id:"assertion-annotation",level:2},{value:"@EnumType annotation",id:"enumtype-annotation",level:2}],y={toc:g},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},y,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query-annotation"},"@Query annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation-annotation"},"@Mutation annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type-annotation"},"@Type annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, ',(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/external-type-declaration"},"the class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype-annotation"},"@ExtendType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input-annotation"},"@Input annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true")," if name is not specified. Whether the annotated PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("em",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation.")))),(0,l.yg)("h2",{id:"field-annotation"},"@Field annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield-annotation"},"@SourceField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield-annotation"},"@MagicField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged-annotation"},"@Logged annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right-annotation"},"@Right annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith-annotation"},"@FailWith annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized-annotation"},"@HideIfUnauthorized annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser-annotation"},"@InjectUser annotation"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security-annotation"},"@Security annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory-annotation"},"@Factory annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype-annotation"},"@UseInputType annotation"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate-annotation"},"@Decorate annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire-annotation"},"@Autowire annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter-annotation"},"@HideParameter annotation"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate-annotation"},"@Validate annotation"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion-annotation"},"@Assertion annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype-annotation"},"@EnumType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9624],{50915:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>g});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,o={unversionedId:"annotations-reference",id:"version-4.3/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-4.3/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/4.3/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/annotations-reference.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"version-4.3/docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/4.3/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/4.3/semver"}},p={},g=[{value:"@Query annotation",id:"query-annotation",level:2},{value:"@Mutation annotation",id:"mutation-annotation",level:2},{value:"@Type annotation",id:"type-annotation",level:2},{value:"@ExtendType annotation",id:"extendtype-annotation",level:2},{value:"@Input annotation",id:"input-annotation",level:2},{value:"@Field annotation",id:"field-annotation",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"@Logged annotation",id:"logged-annotation",level:2},{value:"@Right annotation",id:"right-annotation",level:2},{value:"@FailWith annotation",id:"failwith-annotation",level:2},{value:"@HideIfUnauthorized annotation",id:"hideifunauthorized-annotation",level:2},{value:"@InjectUser annotation",id:"injectuser-annotation",level:2},{value:"@Security annotation",id:"security-annotation",level:2},{value:"@Factory annotation",id:"factory-annotation",level:2},{value:"@UseInputType annotation",id:"useinputtype-annotation",level:2},{value:"@Decorate annotation",id:"decorate-annotation",level:2},{value:"@Autowire annotation",id:"autowire-annotation",level:2},{value:"@HideParameter annotation",id:"hideparameter-annotation",level:2},{value:"@Validate annotation",id:"validate-annotation",level:2},{value:"@Assertion annotation",id:"assertion-annotation",level:2},{value:"@EnumType annotation",id:"enumtype-annotation",level:2}],y={toc:g},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},y,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query-annotation"},"@Query annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation-annotation"},"@Mutation annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type-annotation"},"@Type annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, ',(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/external-type-declaration"},"the class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype-annotation"},"@ExtendType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input-annotation"},"@Input annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true")," if name is not specified. Whether the annotated PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("em",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation.")))),(0,l.yg)("h2",{id:"field-annotation"},"@Field annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield-annotation"},"@SourceField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield-annotation"},"@MagicField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged-annotation"},"@Logged annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right-annotation"},"@Right annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith-annotation"},"@FailWith annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized-annotation"},"@HideIfUnauthorized annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser-annotation"},"@InjectUser annotation"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security-annotation"},"@Security annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory-annotation"},"@Factory annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.3/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype-annotation"},"@UseInputType annotation"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate-annotation"},"@Decorate annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire-annotation"},"@Autowire annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter-annotation"},"@HideParameter annotation"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate-annotation"},"@Validate annotation"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion-annotation"},"@Assertion annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype-annotation"},"@EnumType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2ef99682.38a680cf.js b/assets/js/2ef99682.ef9dafe1.js similarity index 98% rename from assets/js/2ef99682.38a680cf.js rename to assets/js/2ef99682.ef9dafe1.js index b712df7268..63b0c16c5b 100644 --- a/assets/js/2ef99682.38a680cf.js +++ b/assets/js/2ef99682.ef9dafe1.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9092],{27486:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>u,contentTitle:()=>s,default:()=>d,frontMatter:()=>o,metadata:()=>i,toc:()=>l});var n=a(58168),p=(a(96540),a(15680));a(67443);const o={id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"},s=void 0,i={unversionedId:"custom-output-types",id:"version-3.0/custom-output-types",title:"Custom output types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-3.0/custom_output_types.md",sourceDirName:".",slug:"/custom-output-types",permalink:"/docs/3.0/custom-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/custom_output_types.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"},sidebar:"version-3.0/docs",previous:{title:"Pagination",permalink:"/docs/3.0/pagination"},next:{title:"Troubleshooting",permalink:"/docs/3.0/troubleshooting"}},u={},l=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3}],r={toc:l},y="wrapper";function d(e){let{components:t,...a}=e;return(0,p.yg)(y,(0,n.A)({},r,a,{components:t,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field(name="id")\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n')),(0,p.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,p.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,p.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,p.yg)("p",null,"GraphQL comes with an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,p.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID!")\n */\n')),(0,p.yg)("h2",{id:"usage"},"Usage"),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,p.yg)("p",null,"You can use the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Query")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Field")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@SourceField"))),(0,p.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,p.yg)("p",null,"In order to create a custom output type, you need to:"),(0,p.yg)("ol",null,(0,p.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,p.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,p.yg)("p",null,"You'll find more details on the ",(0,p.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,p.yg)("hr",null),(0,p.yg)("p",null,"In order to find existing types, the schema is using ",(0,p.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,p.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,p.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,p.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,p.yg)("p",null,"Any class extending ",(0,p.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,p.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,p.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,p.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,p.yg)("p",null,"The easiest way is to use a ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". This class is used to register custom output types."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper")," instance MUST be registered in your container and linked to a ",(0,p.yg)("inlineCode",{parentName:"p"},"CompositeTypeMapper"),"\nthat will aggregate all the type mappers of the application."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9092],{27486:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>u,contentTitle:()=>s,default:()=>d,frontMatter:()=>o,metadata:()=>i,toc:()=>l});var n=a(58168),p=(a(96540),a(15680));a(67443);const o={id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"},s=void 0,i={unversionedId:"custom-output-types",id:"version-3.0/custom-output-types",title:"Custom output types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-3.0/custom_output_types.md",sourceDirName:".",slug:"/custom-output-types",permalink:"/docs/3.0/custom-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/custom_output_types.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"},sidebar:"version-3.0/docs",previous:{title:"Pagination",permalink:"/docs/3.0/pagination"},next:{title:"Troubleshooting",permalink:"/docs/3.0/troubleshooting"}},u={},l=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3}],r={toc:l},y="wrapper";function d(e){let{components:t,...a}=e;return(0,p.yg)(y,(0,n.A)({},r,a,{components:t,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field(name="id")\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n')),(0,p.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,p.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,p.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,p.yg)("p",null,"GraphQL comes with an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,p.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID!")\n */\n')),(0,p.yg)("h2",{id:"usage"},"Usage"),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,p.yg)("p",null,"You can use the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Query")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Field")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@SourceField"))),(0,p.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,p.yg)("p",null,"In order to create a custom output type, you need to:"),(0,p.yg)("ol",null,(0,p.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,p.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,p.yg)("p",null,"You'll find more details on the ",(0,p.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,p.yg)("hr",null),(0,p.yg)("p",null,"In order to find existing types, the schema is using ",(0,p.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,p.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,p.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,p.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,p.yg)("p",null,"Any class extending ",(0,p.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,p.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,p.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,p.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,p.yg)("p",null,"The easiest way is to use a ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". This class is used to register custom output types."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper")," instance MUST be registered in your container and linked to a ",(0,p.yg)("inlineCode",{parentName:"p"},"CompositeTypeMapper"),"\nthat will aggregate all the type mappers of the application."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2f36012a.a4789889.js b/assets/js/2f36012a.20cd1d0e.js similarity index 89% rename from assets/js/2f36012a.a4789889.js rename to assets/js/2f36012a.20cd1d0e.js index 9999316379..b292e7fbf4 100644 --- a/assets/js/2f36012a.a4789889.js +++ b/assets/js/2f36012a.20cd1d0e.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1674],{52095:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>u});var i=n(58168),r=(n(96540),n(15680));n(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-5.0/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories.",source:"@site/versioned_docs/version-5.0/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/5.0/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/implementing-security.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},sidebar:"version-5.0/docs",previous:{title:"Fine grained security",permalink:"/docs/5.0/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/5.0/query-plan"}},c={},u=[],l={toc:u},p="wrapper";function h(e){let{components:t,...n}=e;return(0,r.yg)(p,(0,i.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories."),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1674],{52095:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),r=(i(96540),i(15680));i(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-5.0/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories.",source:"@site/versioned_docs/version-5.0/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/5.0/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/implementing-security.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},sidebar:"version-5.0/docs",previous:{title:"Fine grained security",permalink:"/docs/5.0/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/5.0/query-plan"}},c={},l=[],u={toc:l},p="wrapper";function h(e){let{components:t,...i}=e;return(0,r.yg)(p,(0,n.A)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories."),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/30940d42.1645291e.js b/assets/js/30940d42.eade2ecb.js similarity index 98% rename from assets/js/30940d42.1645291e.js rename to assets/js/30940d42.eade2ecb.js index 76ef836566..ae1f872517 100644 --- a/assets/js/30940d42.1645291e.js +++ b/assets/js/30940d42.eade2ecb.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7483],{5259:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-5.0/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-5.0/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/5.0/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/symfony-bundle.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"version-5.0/docs",previous:{title:"Getting Started",permalink:"/docs/5.0/getting-started"},next:{title:"Laravel package",permalink:"/docs/5.0/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-5.0/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-5.0/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/5.0/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/symfony-bundle.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"version-5.0/docs",previous:{title:"Getting Started",permalink:"/docs/5.0/getting-started"},next:{title:"Laravel package",permalink:"/docs/5.0/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>c,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started",original_id:"getting-started"},o=void 0,s={unversionedId:"getting-started",id:"version-4.0/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-4.0/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/4.0/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/getting-started.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started",original_id:"getting-started"},sidebar:"version-4.0/docs",previous:{title:"GraphQLite",permalink:"/docs/4.0/"},next:{title:"Symfony bundle",permalink:"/docs/4.0/symfony-bundle"}},d={},l=[],g={toc:l},p="wrapper";function c(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,r.A)({},g,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/universal_service_providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/other-frameworks"},"Get started with another framework (or no framework)"))))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6972],{24664:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>p,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started",original_id:"getting-started"},o=void 0,s={unversionedId:"getting-started",id:"version-4.0/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-4.0/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/4.0/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/getting-started.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started",original_id:"getting-started"},sidebar:"version-4.0/docs",previous:{title:"GraphQLite",permalink:"/docs/4.0/"},next:{title:"Symfony bundle",permalink:"/docs/4.0/symfony-bundle"}},d={},l=[],g={toc:l},c="wrapper";function p(e){let{components:t,...a}=e;return(0,i.yg)(c,(0,r.A)({},g,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/universal_service_providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/other-frameworks"},"Get started with another framework (or no framework)"))))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/323a980a.4cafa56c.js b/assets/js/323a980a.ec878f2d.js similarity index 63% rename from assets/js/323a980a.4cafa56c.js rename to assets/js/323a980a.ec878f2d.js index 75fc5025a7..4f8a4cf36e 100644 --- a/assets/js/323a980a.4cafa56c.js +++ b/assets/js/323a980a.ec878f2d.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5362],{19840:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>d,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var o=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"subscriptions",title:"Subscriptions",sidebar_label:"Subscriptions"},s=void 0,a={unversionedId:"subscriptions",id:"subscriptions",title:"Subscriptions",description:"In GraphQLite, subscriptions are created like queries or mutations.",source:"@site/docs/subscriptions.mdx",sourceDirName:".",slug:"/subscriptions",permalink:"/docs/next/subscriptions",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/subscriptions.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"subscriptions",title:"Subscriptions",sidebar_label:"Subscriptions"},sidebar:"docs",previous:{title:"Mutations",permalink:"/docs/next/mutations"},next:{title:"Type mapping",permalink:"/docs/next/type-mapping"}},p={},c=[],l={toc:c},u="wrapper";function d(e){let{components:t,...n}=e;return(0,i.yg)(u,(0,o.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In GraphQLite, subscriptions are created ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/queries"},"like queries")," or ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/mutations"},"mutations"),"."),(0,i.yg)("p",null,"To create a subscription, you must annotate a method in a controller with the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Subscription]")," attribute."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Subscription(outputType: 'Product')]\n public function productAdded(?ID $categoryId = null): void\n {\n // Some code that sets up any connections, stores the subscription details, etc.\n }\n}\n")),(0,i.yg)("p",null,"As you will notice in the above example, we're returning ",(0,i.yg)("inlineCode",{parentName:"p"},"void"),". In general, this is probably the\ncorrect return type."),(0,i.yg)("p",null,"You could, however, type the ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," as the return type of the method, instead\nof using the ",(0,i.yg)("inlineCode",{parentName:"p"},"outputType")," argument on the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Subscription]")," attribute. This means you\nwould have to return an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," from the method though. One exception here, is if\nyou intend to use PHP for your long-running streaming process, you could block the process inside\nthe controller and basically never return anything from the method, just terminating the\nconnection/stream when it breaks, or when the client disconnects."),(0,i.yg)("p",null,"Most implementations will want to offload the actual real-time streaming connection to a better suited\ntechnology, like SSE (server-sent events), WebSockets, etc. GraphQLite does not make any assumptions\nhere. Therefore, it's most practical to return ",(0,i.yg)("inlineCode",{parentName:"p"},"void")," from the controller method. Since GraphQL\nis a strictly typed spec, we cannot return anything other than the defined ",(0,i.yg)("inlineCode",{parentName:"p"},"outputType")," from the request.\nThat would be a violation of the GraphQL specification. Returning ",(0,i.yg)("inlineCode",{parentName:"p"},"void"),", which is translated to ",(0,i.yg)("inlineCode",{parentName:"p"},"null"),"\nin the GraphQL response body, allows for us to complete the request and terminate the PHP process."),(0,i.yg)("p",null,"We recommend using response headers to pass back any necessary information realted to the subscription.\nThis might be a subscription ID, a streaming server URL to connect to, or whatever you need to pass\nback to the client."),(0,i.yg)("div",{class:"alert alert--info"},"In the future, it may make sense to implement streaming servers directly into GraphQLite, especially as PHP progresses with async and parallel processing. At this time, we might consider returning a `Generator` (or `Fiber`) from the controller method."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5362],{19840:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>d,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var i=n(58168),o=(n(96540),n(15680));n(67443);const r={id:"subscriptions",title:"Subscriptions",sidebar_label:"Subscriptions"},s=void 0,a={unversionedId:"subscriptions",id:"subscriptions",title:"Subscriptions",description:"In GraphQLite, subscriptions are created like queries or mutations.",source:"@site/docs/subscriptions.mdx",sourceDirName:".",slug:"/subscriptions",permalink:"/docs/next/subscriptions",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/subscriptions.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"subscriptions",title:"Subscriptions",sidebar_label:"Subscriptions"},sidebar:"docs",previous:{title:"Mutations",permalink:"/docs/next/mutations"},next:{title:"Type mapping",permalink:"/docs/next/type-mapping"}},p={},c=[],l={toc:c},u="wrapper";function d(e){let{components:t,...n}=e;return(0,o.yg)(u,(0,i.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In GraphQLite, subscriptions are created ",(0,o.yg)("a",{parentName:"p",href:"/docs/next/queries"},"like queries")," or ",(0,o.yg)("a",{parentName:"p",href:"/docs/next/mutations"},"mutations"),"."),(0,o.yg)("p",null,"To create a subscription, you must annotate a method in a controller with the ",(0,o.yg)("inlineCode",{parentName:"p"},"#[Subscription]")," attribute."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Subscription(outputType: 'Product')]\n public function productAdded(?ID $categoryId = null): void\n {\n // Some code that sets up any connections, stores the subscription details, etc.\n }\n}\n")),(0,o.yg)("p",null,"As you will notice in the above example, we're returning ",(0,o.yg)("inlineCode",{parentName:"p"},"void"),". In general, this is probably the\ncorrect return type."),(0,o.yg)("p",null,"You could, however, type the ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," as the return type of the method, instead\nof using the ",(0,o.yg)("inlineCode",{parentName:"p"},"outputType")," argument on the ",(0,o.yg)("inlineCode",{parentName:"p"},"#[Subscription]")," attribute. This means you\nwould have to return an instance of ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," from the method though. One exception here, is if\nyou intend to use PHP for your long-running streaming process, you could block the process inside\nthe controller and basically never return anything from the method, just terminating the\nconnection/stream when it breaks, or when the client disconnects."),(0,o.yg)("p",null,"Most implementations will want to offload the actual real-time streaming connection to a better suited\ntechnology, like SSE (server-sent events), WebSockets, etc. GraphQLite does not make any assumptions\nhere. Therefore, it's most practical to return ",(0,o.yg)("inlineCode",{parentName:"p"},"void")," from the controller method. Since GraphQL\nis a strictly typed spec, we cannot return anything other than the defined ",(0,o.yg)("inlineCode",{parentName:"p"},"outputType")," from the request.\nThat would be a violation of the GraphQL specification. Returning ",(0,o.yg)("inlineCode",{parentName:"p"},"void"),", which is translated to ",(0,o.yg)("inlineCode",{parentName:"p"},"null"),"\nin the GraphQL response body, allows for us to complete the request and terminate the PHP process."),(0,o.yg)("p",null,"We recommend using response headers to pass back any necessary information realted to the subscription.\nThis might be a subscription ID, a streaming server URL to connect to, or whatever you need to pass\nback to the client."),(0,o.yg)("div",{class:"alert alert--info"},"In the future, it may make sense to implement streaming servers directly into GraphQLite, especially as PHP progresses with async and parallel processing. At this time, we might consider returning a `Generator` (or `Fiber`) from the controller method."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/332827b4.28485cff.js b/assets/js/332827b4.a14565af.js similarity index 63% rename from assets/js/332827b4.28485cff.js rename to assets/js/332827b4.a14565af.js index d20b13385f..ac8aca30f6 100644 --- a/assets/js/332827b4.28485cff.js +++ b/assets/js/332827b4.a14565af.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4780],{14335:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>d,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var o=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"subscriptions",title:"Subscriptions",sidebar_label:"Subscriptions"},s=void 0,a={unversionedId:"subscriptions",id:"version-7.0.0/subscriptions",title:"Subscriptions",description:"In GraphQLite, subscriptions are created like queries or mutations.",source:"@site/versioned_docs/version-7.0.0/subscriptions.mdx",sourceDirName:".",slug:"/subscriptions",permalink:"/docs/subscriptions",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/subscriptions.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"subscriptions",title:"Subscriptions",sidebar_label:"Subscriptions"},sidebar:"docs",previous:{title:"Mutations",permalink:"/docs/mutations"},next:{title:"Type mapping",permalink:"/docs/type-mapping"}},p={},c=[],l={toc:c},u="wrapper";function d(e){let{components:t,...n}=e;return(0,i.yg)(u,(0,o.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In GraphQLite, subscriptions are created ",(0,i.yg)("a",{parentName:"p",href:"/docs/queries"},"like queries")," or ",(0,i.yg)("a",{parentName:"p",href:"/docs/mutations"},"mutations"),"."),(0,i.yg)("p",null,"To create a subscription, you must annotate a method in a controller with the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Subscription]")," attribute."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Subscription(outputType: 'Product')]\n public function productAdded(?ID $categoryId = null): void\n {\n // Some code that sets up any connections, stores the subscription details, etc.\n }\n}\n")),(0,i.yg)("p",null,"As you will notice in the above example, we're returning ",(0,i.yg)("inlineCode",{parentName:"p"},"void"),". In general, this is probably the\ncorrect return type."),(0,i.yg)("p",null,"You could, however, type the ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," as the return type of the method, instead\nof using the ",(0,i.yg)("inlineCode",{parentName:"p"},"outputType")," argument on the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Subscription]")," attribute. This means you\nwould have to return an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," from the method though. One exception here, is if\nyou intend to use PHP for your long-running streaming process, you could block the process inside\nthe controller and basically never return anything from the method, just terminating the\nconnection/stream when it breaks, or when the client disconnects."),(0,i.yg)("p",null,"Most implementations will want to offload the actual real-time streaming connection to a better suited\ntechnology, like SSE (server-sent events), WebSockets, etc. GraphQLite does not make any assumptions\nhere. Therefore, it's most practical to return ",(0,i.yg)("inlineCode",{parentName:"p"},"void")," from the controller method. Since GraphQL\nis a strictly typed spec, we cannot return anything other than the defined ",(0,i.yg)("inlineCode",{parentName:"p"},"outputType")," from the request.\nThat would be a violation of the GraphQL specification. Returning ",(0,i.yg)("inlineCode",{parentName:"p"},"void"),", which is translated to ",(0,i.yg)("inlineCode",{parentName:"p"},"null"),"\nin the GraphQL response body, allows for us to complete the request and terminate the PHP process."),(0,i.yg)("p",null,"We recommend using response headers to pass back any necessary information realted to the subscription.\nThis might be a subscription ID, a streaming server URL to connect to, or whatever you need to pass\nback to the client."),(0,i.yg)("div",{class:"alert alert--info"},"In the future, it may make sense to implement streaming servers directly into GraphQLite, especially as PHP progresses with async and parallel processing. At this time, we might consider returning a `Generator` (or `Fiber`) from the controller method."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4780],{14335:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>d,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var i=n(58168),o=(n(96540),n(15680));n(67443);const r={id:"subscriptions",title:"Subscriptions",sidebar_label:"Subscriptions"},s=void 0,a={unversionedId:"subscriptions",id:"version-7.0.0/subscriptions",title:"Subscriptions",description:"In GraphQLite, subscriptions are created like queries or mutations.",source:"@site/versioned_docs/version-7.0.0/subscriptions.mdx",sourceDirName:".",slug:"/subscriptions",permalink:"/docs/subscriptions",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/subscriptions.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"subscriptions",title:"Subscriptions",sidebar_label:"Subscriptions"},sidebar:"docs",previous:{title:"Mutations",permalink:"/docs/mutations"},next:{title:"Type mapping",permalink:"/docs/type-mapping"}},p={},c=[],l={toc:c},u="wrapper";function d(e){let{components:t,...n}=e;return(0,o.yg)(u,(0,i.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In GraphQLite, subscriptions are created ",(0,o.yg)("a",{parentName:"p",href:"/docs/queries"},"like queries")," or ",(0,o.yg)("a",{parentName:"p",href:"/docs/mutations"},"mutations"),"."),(0,o.yg)("p",null,"To create a subscription, you must annotate a method in a controller with the ",(0,o.yg)("inlineCode",{parentName:"p"},"#[Subscription]")," attribute."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Subscription(outputType: 'Product')]\n public function productAdded(?ID $categoryId = null): void\n {\n // Some code that sets up any connections, stores the subscription details, etc.\n }\n}\n")),(0,o.yg)("p",null,"As you will notice in the above example, we're returning ",(0,o.yg)("inlineCode",{parentName:"p"},"void"),". In general, this is probably the\ncorrect return type."),(0,o.yg)("p",null,"You could, however, type the ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," as the return type of the method, instead\nof using the ",(0,o.yg)("inlineCode",{parentName:"p"},"outputType")," argument on the ",(0,o.yg)("inlineCode",{parentName:"p"},"#[Subscription]")," attribute. This means you\nwould have to return an instance of ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," from the method though. One exception here, is if\nyou intend to use PHP for your long-running streaming process, you could block the process inside\nthe controller and basically never return anything from the method, just terminating the\nconnection/stream when it breaks, or when the client disconnects."),(0,o.yg)("p",null,"Most implementations will want to offload the actual real-time streaming connection to a better suited\ntechnology, like SSE (server-sent events), WebSockets, etc. GraphQLite does not make any assumptions\nhere. Therefore, it's most practical to return ",(0,o.yg)("inlineCode",{parentName:"p"},"void")," from the controller method. Since GraphQL\nis a strictly typed spec, we cannot return anything other than the defined ",(0,o.yg)("inlineCode",{parentName:"p"},"outputType")," from the request.\nThat would be a violation of the GraphQL specification. Returning ",(0,o.yg)("inlineCode",{parentName:"p"},"void"),", which is translated to ",(0,o.yg)("inlineCode",{parentName:"p"},"null"),"\nin the GraphQL response body, allows for us to complete the request and terminate the PHP process."),(0,o.yg)("p",null,"We recommend using response headers to pass back any necessary information realted to the subscription.\nThis might be a subscription ID, a streaming server URL to connect to, or whatever you need to pass\nback to the client."),(0,o.yg)("div",{class:"alert alert--info"},"In the future, it may make sense to implement streaming servers directly into GraphQLite, especially as PHP progresses with async and parallel processing. At this time, we might consider returning a `Generator` (or `Fiber`) from the controller method."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/346bcb92.3d2a49dd.js b/assets/js/346bcb92.db7b61ce.js similarity index 98% rename from assets/js/346bcb92.3d2a49dd.js rename to assets/js/346bcb92.db7b61ce.js index edba7d9954..53669995c5 100644 --- a/assets/js/346bcb92.3d2a49dd.js +++ b/assets/js/346bcb92.db7b61ce.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8548],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),s=a(56347),i=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function g(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,s.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,s]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,u]=m({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=i??p;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&s(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==s&&(p(t),i(n))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":s===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,f.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},2417:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>s,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const s={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},i=void 0,u={unversionedId:"pagination",id:"version-5.0/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-5.0/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/5.0/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/pagination.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},sidebar:"version-5.0/docs",previous:{title:"File uploads",permalink:"/docs/5.0/file-uploads"},next:{title:"Custom types",permalink:"/docs/5.0/custom-types"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(g,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,r.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,r.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,r.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,r.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"You will need to install the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"In your query, simply return a class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,r.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,r.yg)("p",null,'The "count" field returns the ',(0,r.yg)("strong",{parentName:"p"},"total count")," of items."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8548],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),s=a(56347),i=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function g(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,s.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,s]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,u]=m({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=i??p;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&s(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==s&&(p(t),i(n))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":s===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,f.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},2417:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>s,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const s={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},i=void 0,u={unversionedId:"pagination",id:"version-5.0/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-5.0/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/5.0/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/pagination.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},sidebar:"version-5.0/docs",previous:{title:"File uploads",permalink:"/docs/5.0/file-uploads"},next:{title:"Custom types",permalink:"/docs/5.0/custom-types"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(g,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,r.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,r.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,r.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,r.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"You will need to install the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"In your query, simply return a class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,r.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,r.yg)("p",null,'The "count" field returns the ',(0,r.yg)("strong",{parentName:"p"},"total count")," of items."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/354a9b78.bf15845e.js b/assets/js/354a9b78.31211d7c.js similarity index 97% rename from assets/js/354a9b78.bf15845e.js rename to assets/js/354a9b78.31211d7c.js index be00fa1662..64040facca 100644 --- a/assets/js/354a9b78.bf15845e.js +++ b/assets/js/354a9b78.31211d7c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7942],{952:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>d});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"extend_input_type",title:"Extending an input type",sidebar_label:"Extending an input type",original_id:"extend_input_type"},r=void 0,l={unversionedId:"extend_input_type",id:"version-4.0/extend_input_type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.0/extend_input_type.mdx",sourceDirName:".",slug:"/extend_input_type",permalink:"/docs/4.0/extend_input_type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/extend_input_type.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend_input_type",title:"Extending an input type",sidebar_label:"Extending an input type",original_id:"extend_input_type"},sidebar:"version-4.0/docs",previous:{title:"Custom argument resolving",permalink:"/docs/4.0/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/4.0/multiple_output_types"}},p={},d=[],y={toc:d},s="wrapper";function u(e){let{components:t,...n}=e;return(0,i.yg)(s,(0,a.A)({},y,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("small",null,"Available in GraphQLite 4.0+"),(0,i.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,i.yg)("code",null,"@Factory")," tag, ",(0,i.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,i.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,i.yg)("p",null,"Just like with output type (that can be ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/extend_type"},"extended using the ",(0,i.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,i.yg)("div",{class:"alert alert--info"},"The ",(0,i.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,i.yg)("code",null,"@Factory")," method. This can happen if the ",(0,i.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,i.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,i.yg)("p",null,"Let's assume you have a ",(0,i.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")),(0,i.yg)("p",null,"Assuming you ",(0,i.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")),(0,i.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,i.yg)("p",null,"A few things to notice:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,i.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,i.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,i.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7942],{952:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>d});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"extend_input_type",title:"Extending an input type",sidebar_label:"Extending an input type",original_id:"extend_input_type"},r=void 0,l={unversionedId:"extend_input_type",id:"version-4.0/extend_input_type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.0/extend_input_type.mdx",sourceDirName:".",slug:"/extend_input_type",permalink:"/docs/4.0/extend_input_type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/extend_input_type.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend_input_type",title:"Extending an input type",sidebar_label:"Extending an input type",original_id:"extend_input_type"},sidebar:"version-4.0/docs",previous:{title:"Custom argument resolving",permalink:"/docs/4.0/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/4.0/multiple_output_types"}},p={},d=[],y={toc:d},s="wrapper";function u(e){let{components:t,...n}=e;return(0,i.yg)(s,(0,a.A)({},y,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("small",null,"Available in GraphQLite 4.0+"),(0,i.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,i.yg)("code",null,"@Factory")," tag, ",(0,i.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,i.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,i.yg)("p",null,"Just like with output type (that can be ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/extend_type"},"extended using the ",(0,i.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,i.yg)("div",{class:"alert alert--info"},"The ",(0,i.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,i.yg)("code",null,"@Factory")," method. This can happen if the ",(0,i.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,i.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,i.yg)("p",null,"Let's assume you have a ",(0,i.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")),(0,i.yg)("p",null,"Assuming you ",(0,i.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")),(0,i.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,i.yg)("p",null,"A few things to notice:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,i.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,i.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,i.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/366cfce3.dba6ab85.js b/assets/js/366cfce3.cb9991ef.js similarity index 99% rename from assets/js/366cfce3.dba6ab85.js rename to assets/js/366cfce3.cb9991ef.js index ce92146abe..a7f2e3ce9a 100644 --- a/assets/js/366cfce3.dba6ab85.js +++ b/assets/js/366cfce3.cb9991ef.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5180],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),i=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},3190:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const i={id:"queries",title:"Queries",sidebar_label:"Queries"},s=void 0,u={unversionedId:"queries",id:"version-4.2/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-4.2/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/4.2/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/queries.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"version-4.2/docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/4.2/other-frameworks"},next:{title:"Mutations",permalink:"/docs/4.2/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...i}=e;return(0,r.yg)(h,(0,a.A)({},d,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5180],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),i=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},3190:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const i={id:"queries",title:"Queries",sidebar_label:"Queries"},s=void 0,u={unversionedId:"queries",id:"version-4.2/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-4.2/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/4.2/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/queries.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"version-4.2/docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/4.2/other-frameworks"},next:{title:"Mutations",permalink:"/docs/4.2/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...i}=e;return(0,r.yg)(h,(0,a.A)({},d,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file diff --git a/assets/js/36ddade1.44eb30cc.js b/assets/js/36ddade1.67fe7148.js similarity index 83% rename from assets/js/36ddade1.44eb30cc.js rename to assets/js/36ddade1.67fe7148.js index ec908e4366..f978b21f0f 100644 --- a/assets/js/36ddade1.44eb30cc.js +++ b/assets/js/36ddade1.67fe7148.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2917],{73630:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>c,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},o=void 0,s={unversionedId:"getting-started",id:"version-6.0/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-6.0/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/6.0/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/getting-started.md",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},sidebar:"docs",previous:{title:"GraphQLite",permalink:"/docs/6.0/"},next:{title:"Symfony bundle",permalink:"/docs/6.0/symfony-bundle"}},d={},l=[],p={toc:l},g="wrapper";function c(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,r.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/universal-service-providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/other-frameworks"},"Get started with another framework (or no framework)"))))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2917],{73630:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>p,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},o=void 0,s={unversionedId:"getting-started",id:"version-6.0/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-6.0/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/6.0/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/getting-started.md",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},sidebar:"docs",previous:{title:"GraphQLite",permalink:"/docs/6.0/"},next:{title:"Symfony bundle",permalink:"/docs/6.0/symfony-bundle"}},d={},l=[],c={toc:l},g="wrapper";function p(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,r.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/universal-service-providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/other-frameworks"},"Get started with another framework (or no framework)"))))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/379bfe51.3af3b9e7.js b/assets/js/379bfe51.971c848c.js similarity index 98% rename from assets/js/379bfe51.3af3b9e7.js rename to assets/js/379bfe51.971c848c.js index 7fc97fcb50..6856d0f106 100644 --- a/assets/js/379bfe51.3af3b9e7.js +++ b/assets/js/379bfe51.971c848c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8313],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),i=a(56347),s=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function g(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,u]=m({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=s??p;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&i(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==i&&(p(t),s(n))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,f.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},27685:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>i,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const i={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},s=void 0,u={unversionedId:"pagination",id:"version-4.1/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-4.1/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/4.1/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/pagination.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},sidebar:"version-4.1/docs",previous:{title:"File uploads",permalink:"/docs/4.1/file-uploads"},next:{title:"Custom types",permalink:"/docs/4.1/custom-types"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(g,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,r.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,r.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,r.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,r.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"You will need to install the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"In your query, simply return a class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,r.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,r.yg)("p",null,'The "count" field returns the ',(0,r.yg)("strong",{parentName:"p"},"total count")," of items."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8313],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),i=a(56347),s=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function g(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,u]=m({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=s??p;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&i(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==i&&(p(t),s(n))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,f.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},27685:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>i,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const i={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},s=void 0,u={unversionedId:"pagination",id:"version-4.1/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-4.1/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/4.1/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/pagination.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},sidebar:"version-4.1/docs",previous:{title:"File uploads",permalink:"/docs/4.1/file-uploads"},next:{title:"Custom types",permalink:"/docs/4.1/custom-types"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(g,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,r.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,r.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,r.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,r.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"You will need to install the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"In your query, simply return a class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,r.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,r.yg)("p",null,'The "count" field returns the ',(0,r.yg)("strong",{parentName:"p"},"total count")," of items."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/380575ae.84b8cf16.js b/assets/js/380575ae.4091e635.js similarity index 98% rename from assets/js/380575ae.84b8cf16.js rename to assets/js/380575ae.4091e635.js index 3c572144a0..d0b37fb833 100644 --- a/assets/js/380575ae.84b8cf16.js +++ b/assets/js/380575ae.4091e635.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7757],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),s=a(56347),i=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function g(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,s.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,s]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,u]=m({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=i??p;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&s(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==s&&(p(t),i(n))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":s===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,f.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},28669:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>s,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const s={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},i=void 0,u={unversionedId:"pagination",id:"version-7.0.0/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-7.0.0/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/pagination.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},sidebar:"docs",previous:{title:"File uploads",permalink:"/docs/file-uploads"},next:{title:"Custom types",permalink:"/docs/custom-types"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(g,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,r.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,r.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,r.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,r.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"You will need to install the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"In your query, simply return a class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,r.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,r.yg)("p",null,'The "count" field returns the ',(0,r.yg)("strong",{parentName:"p"},"total count")," of items."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7757],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),s=a(56347),i=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function g(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,s.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,s]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,u]=m({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=i??p;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&s(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==s&&(p(t),i(n))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":s===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,f.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},28669:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>s,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const s={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},i=void 0,u={unversionedId:"pagination",id:"version-7.0.0/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-7.0.0/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/pagination.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},sidebar:"docs",previous:{title:"File uploads",permalink:"/docs/file-uploads"},next:{title:"Custom types",permalink:"/docs/custom-types"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(g,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,r.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,r.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,r.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,r.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"You will need to install the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"In your query, simply return a class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,r.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,r.yg)("p",null,'The "count" field returns the ',(0,r.yg)("strong",{parentName:"p"},"total count")," of items."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/38317547.182d255d.js b/assets/js/38317547.188d5a77.js similarity index 96% rename from assets/js/38317547.182d255d.js rename to assets/js/38317547.188d5a77.js index 60a75c49dd..2f0376a1c1 100644 --- a/assets/js/38317547.182d255d.js +++ b/assets/js/38317547.188d5a77.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4225],{99983:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>r,metadata:()=>l,toc:()=>o});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"},s=void 0,l={unversionedId:"inheritance",id:"version-4.1/inheritance",title:"Inheritance and interfaces",description:"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces.",source:"@site/versioned_docs/version-4.1/inheritance.md",sourceDirName:".",slug:"/inheritance",permalink:"/docs/4.1/inheritance",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/inheritance.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"}},c={},o=[],p={toc:o},d="wrapper";function h(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("div",{class:"alert alert--warning"},"Right now, there is no way to explicitly declare a GraphQL interface using GraphQLite.",(0,i.yg)("br",null),"GraphQLite automatically declares interfaces when it sees an inheritance relationship between to classes that are GraphQL types."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4225],{99983:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>r,metadata:()=>l,toc:()=>o});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"},s=void 0,l={unversionedId:"inheritance",id:"version-4.1/inheritance",title:"Inheritance and interfaces",description:"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces.",source:"@site/versioned_docs/version-4.1/inheritance.md",sourceDirName:".",slug:"/inheritance",permalink:"/docs/4.1/inheritance",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/inheritance.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"}},c={},o=[],p={toc:o},d="wrapper";function h(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("div",{class:"alert alert--warning"},"Right now, there is no way to explicitly declare a GraphQL interface using GraphQLite.",(0,i.yg)("br",null),"GraphQLite automatically declares interfaces when it sees an inheritance relationship between to classes that are GraphQL types."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/38cf1c7a.5f2325ce.js b/assets/js/38cf1c7a.b6b75450.js similarity index 97% rename from assets/js/38cf1c7a.5f2325ce.js rename to assets/js/38cf1c7a.b6b75450.js index c42d019f63..079028edb9 100644 --- a/assets/js/38cf1c7a.5f2325ce.js +++ b/assets/js/38cf1c7a.b6b75450.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5409],{41174:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var t=i(58168),a=(i(96540),i(15680));i(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations",original_id:"field-middlewares"},r=void 0,o={unversionedId:"field-middlewares",id:"version-4.0/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.0/field_middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/4.0/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/field_middlewares.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations",original_id:"field-middlewares"},sidebar:"version-4.0/docs",previous:{title:"Custom types",permalink:"/docs/4.0/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/4.0/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,t.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour\nof a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:i(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library."),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"OnlyDebug.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Annotations;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface\nis a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation\nis to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:",(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"li"},"graphql.field_middleware")," tag.")))}c.isMDXComponent=!0},8643:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5409],{41174:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var t=i(58168),a=(i(96540),i(15680));i(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations",original_id:"field-middlewares"},r=void 0,o={unversionedId:"field-middlewares",id:"version-4.0/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.0/field_middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/4.0/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/field_middlewares.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations",original_id:"field-middlewares"},sidebar:"version-4.0/docs",previous:{title:"Custom types",permalink:"/docs/4.0/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/4.0/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,t.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour\nof a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:i(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library."),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"OnlyDebug.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Annotations;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface\nis a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation\nis to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:",(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"li"},"graphql.field_middleware")," tag.")))}c.isMDXComponent=!0},8643:(e,n,i)=>{i.d(n,{A:()=>t});const t=i.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file diff --git a/assets/js/394f3211.5f9ae7dc.js b/assets/js/394f3211.eb368321.js similarity index 98% rename from assets/js/394f3211.5f9ae7dc.js rename to assets/js/394f3211.eb368321.js index c9509f19f3..e1a26d6d1d 100644 --- a/assets/js/394f3211.5f9ae7dc.js +++ b/assets/js/394f3211.eb368321.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6700],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>q});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),i=a(56347),s=a(57485),u=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:a,groupId:n}),[c,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=u[a].value;n!==i&&(c(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function q(e){const t=(0,m.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},71103:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>i,toc:()=>u});var n=a(58168),r=(a(96540),a(15680));a(67443),a(11470),a(19365);const l={id:"queries",title:"Queries",sidebar_label:"Queries"},o=void 0,i={unversionedId:"queries",id:"version-6.1/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-6.1/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/6.1/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/queries.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/6.1/other-frameworks"},next:{title:"Mutations",permalink:"/docs/6.1/mutations"}},s={},u=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],p={toc:u},c="wrapper";function d(e){let{components:t,...l}=e;return(0,r.yg)(c,(0,n.A)({},p,l,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:a(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}d.isMDXComponent=!0},67258:(e,t,a)=>{a.d(t,{A:()=>n});const n=a.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6700],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>q});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),i=a(56347),s=a(57485),u=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:a,groupId:n}),[c,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=u[a].value;n!==i&&(c(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function q(e){const t=(0,m.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},71103:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>i,toc:()=>u});var n=a(58168),r=(a(96540),a(15680));a(67443),a(11470),a(19365);const l={id:"queries",title:"Queries",sidebar_label:"Queries"},o=void 0,i={unversionedId:"queries",id:"version-6.1/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-6.1/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/6.1/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/queries.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/6.1/other-frameworks"},next:{title:"Mutations",permalink:"/docs/6.1/mutations"}},s={},u=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],p={toc:u},c="wrapper";function d(e){let{components:t,...l}=e;return(0,r.yg)(c,(0,n.A)({},p,l,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:a(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}d.isMDXComponent=!0},67258:(e,t,a)=>{a.d(t,{A:()=>n});const n=a.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file diff --git a/assets/js/3b486936.8548a8fa.js b/assets/js/3b486936.14f5c295.js similarity index 88% rename from assets/js/3b486936.8548a8fa.js rename to assets/js/3b486936.14f5c295.js index afd59fb795..21a24343b2 100644 --- a/assets/js/3b486936.8548a8fa.js +++ b/assets/js/3b486936.14f5c295.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8100],{70177:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-5.0/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-5.0/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/5.0/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/troubleshooting.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"version-5.0/docs",previous:{title:"Internals",permalink:"/docs/5.0/internals"},next:{title:"Migrating",permalink:"/docs/5.0/migrating"}},l={},u=[],p={toc:u},c="wrapper";function d(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,n.A)({},p,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8100],{70177:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>g,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-5.0/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-5.0/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/5.0/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/troubleshooting.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"version-5.0/docs",previous:{title:"Internals",permalink:"/docs/5.0/internals"},next:{title:"Migrating",permalink:"/docs/5.0/migrating"}},l={},u=[],p={toc:u},c="wrapper";function g(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,n.A)({},p,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/3d0eb74d.2794d152.js b/assets/js/3d0eb74d.bfa43dc4.js similarity index 98% rename from assets/js/3d0eb74d.2794d152.js rename to assets/js/3d0eb74d.bfa43dc4.js index f5020e9095..58c8a2454d 100644 --- a/assets/js/3d0eb74d.2794d152.js +++ b/assets/js/3d0eb74d.bfa43dc4.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5558],{10381:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-4.3/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.3/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/4.3/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/symfony-bundle.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"version-4.3/docs",previous:{title:"Getting Started",permalink:"/docs/4.3/getting-started"},next:{title:"Laravel package",permalink:"/docs/4.3/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-4.3/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.3/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/4.3/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/symfony-bundle.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"version-4.3/docs",previous:{title:"Getting Started",permalink:"/docs/4.3/getting-started"},next:{title:"Laravel package",permalink:"/docs/4.3/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>I});var a=n(58168),r=n(96540),i=n(20053),o=n(23104),l=n(56347),u=n(57485),s=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=d(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[u,s]=m({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),h=(()=>{const e=u??c;return y({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{h&&l(h)}),[h]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var h=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:l,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=s[n].value;a!==l&&(c(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},s.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function N(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function I(e){const t=(0,h.A)();return r.createElement(N,(0,a.A)({key:String(t)},e))}},33591:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var a=n(58168),r=(n(96540),n(15680));n(67443),n(11470),n(19365);const i={id:"input-types",title:"Input types",sidebar_label:"Input types"},o=void 0,l={unversionedId:"input-types",id:"version-6.1/input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-6.1/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/6.1/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/input-types.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"docs",previous:{title:"External type declaration",permalink:"/docs/6.1/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/6.1/inheritance-interfaces"}},u={},s=[{value:"#[Input] Attribute",id:"input-attribute",level:2},{value:"Multiple Input Types from the same class",id:"multiple-input-types-from-the-same-class",level:3},{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3}],p={toc:s},c="wrapper";function d(e){let{components:t,...n}=e;return(0,r.yg)(c,(0,a.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,r.yg)("p",null,"Your GraphQL query might look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,r.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,r.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,r.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,r.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,r.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,r.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using the ",(0,r.yg)("a",{parentName:"p",href:"input-attribute"},(0,r.yg)("inlineCode",{parentName:"a"},"#[Input]")," attribute")," or a ",(0,r.yg)("a",{parentName:"p",href:"factory"},"Factory method"),"."),(0,r.yg)("h2",{id:"input-attribute"},"#","[","Input","]"," Attribute"),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute, we can transform the ",(0,r.yg)("inlineCode",{parentName:"p"},"Location")," class, in the example above, into an input type. Just add the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute to the corresponding properties:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private ?string $name = null;\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function setName(string $name): void\n {\n $this->name = $name;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,r.yg)("p",null,"Now if you call the ",(0,r.yg)("inlineCode",{parentName:"p"},"getCities")," query, from the controller in the first example, the ",(0,r.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with the user provided, ",(0,r.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,r.yg)("inlineCode",{parentName:"p"},"longitude")," properties, and passed to the controller as a parameter."),(0,r.yg)("p",null,"There are some important things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotation is recognized on properties for Input Type, as well as setters."),(0,r.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,r.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort - no constructor required."),(0,r.yg)("li",{parentName:"ul"},"For private or protected properties implemented, a public setter is required (if they are not set via the constructor). For example ",(0,r.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),". You can also put the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on the setter, instead of the property, allowing you to have use many other attributes (",(0,r.yg)("inlineCode",{parentName:"li"},"Security"),", ",(0,r.yg)("inlineCode",{parentName:"li"},"Right"),", ",(0,r.yg)("inlineCode",{parentName:"li"},"Autowire"),", etc.)."))),(0,r.yg)("li",{parentName:"ul"},"For validation of these Input Types, see the ",(0,r.yg)("a",{parentName:"li",href:"validation#custom-inputtype-validation"},"Custom InputType Validation section"),"."),(0,r.yg)("li",{parentName:"ul"},"It's advised to use the ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Input]")," attribute on DTO style input type objects and not directly on your model objects. Using it on your model objects can cause coupling in undesirable ways.")),(0,r.yg)("h3",{id:"multiple-input-types-from-the-same-class"},"Multiple Input Types from the same class"),(0,r.yg)("p",null,"Simple usage of the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Input"),' annotation on a class creates a GraphQL input named by class name + "Input" suffix if a class name does not end with it already. Ex. ',(0,r.yg)("inlineCode",{parentName:"p"},"LocationInput")," for ",(0,r.yg)("inlineCode",{parentName:"p"},"Location")," class."),(0,r.yg)("p",null,"You can add multiple ",(0,r.yg)("inlineCode",{parentName:"p"},"@Input")," annotations to the same class, give them different names and link different fields.\nConsider the following example:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n protected ?int $age;\n\n\n #[Field]\n public function setAge(?int $age): void\n {\n $this->age = $age;\n }\n}\n")),(0,r.yg)("p",null,"There are 2 input types added to the ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInput")," class: ",(0,r.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,r.yg)("li",{parentName:"ul"},"Field ",(0,r.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,r.yg)("li",{parentName:"ul"},"Field ",(0,r.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,r.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,r.yg)("li",{parentName:"ul"},"Field ",(0,r.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,r.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,r.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,r.yg)("li",{parentName:"ul"},"Field ",(0,r.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,r.yg)("p",null,"Note that ",(0,r.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,r.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,r.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,r.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."),(0,r.yg)("h2",{id:"factory"},"Factory"),(0,r.yg)("p",null,"A ",(0,r.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,r.yg)("p",null,"Here is an example of factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")),(0,r.yg)("p",null,"and now, you can run query like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,r.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,r.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,r.yg)("p",null,"A few important things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,r.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,r.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,r.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,r.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")),(0,r.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n")),(0,r.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,r.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,r.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,r.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\npublic function getProductById(#[UseInputType(inputType:"ID!")] string $id): Product {\n return $this->productRepository->findById($id);\n}\n')),(0,r.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,r.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,r.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,r.yg)("p",null,"Here is an annotated sample:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')),(0,r.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,r.yg)("p",null,"Image your ",(0,r.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n")),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,r.yg)("p",null,"To be able to hide an argument, the argument must have a default value."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2235],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>I});var a=n(58168),r=n(96540),i=n(20053),o=n(23104),l=n(56347),u=n(57485),s=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=d(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[u,s]=m({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),h=(()=>{const e=u??c;return y({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{h&&l(h)}),[h]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var h=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:l,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=s[n].value;a!==l&&(c(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},s.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function N(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function I(e){const t=(0,h.A)();return r.createElement(N,(0,a.A)({key:String(t)},e))}},33591:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var a=n(58168),r=(n(96540),n(15680));n(67443),n(11470),n(19365);const i={id:"input-types",title:"Input types",sidebar_label:"Input types"},o=void 0,l={unversionedId:"input-types",id:"version-6.1/input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-6.1/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/6.1/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/input-types.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"docs",previous:{title:"External type declaration",permalink:"/docs/6.1/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/6.1/inheritance-interfaces"}},u={},s=[{value:"#[Input] Attribute",id:"input-attribute",level:2},{value:"Multiple Input Types from the same class",id:"multiple-input-types-from-the-same-class",level:3},{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3}],p={toc:s},c="wrapper";function d(e){let{components:t,...n}=e;return(0,r.yg)(c,(0,a.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,r.yg)("p",null,"Your GraphQL query might look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,r.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,r.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,r.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,r.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,r.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,r.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using the ",(0,r.yg)("a",{parentName:"p",href:"input-attribute"},(0,r.yg)("inlineCode",{parentName:"a"},"#[Input]")," attribute")," or a ",(0,r.yg)("a",{parentName:"p",href:"factory"},"Factory method"),"."),(0,r.yg)("h2",{id:"input-attribute"},"#","[","Input","]"," Attribute"),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute, we can transform the ",(0,r.yg)("inlineCode",{parentName:"p"},"Location")," class, in the example above, into an input type. Just add the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute to the corresponding properties:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private ?string $name = null;\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function setName(string $name): void\n {\n $this->name = $name;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,r.yg)("p",null,"Now if you call the ",(0,r.yg)("inlineCode",{parentName:"p"},"getCities")," query, from the controller in the first example, the ",(0,r.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with the user provided, ",(0,r.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,r.yg)("inlineCode",{parentName:"p"},"longitude")," properties, and passed to the controller as a parameter."),(0,r.yg)("p",null,"There are some important things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotation is recognized on properties for Input Type, as well as setters."),(0,r.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,r.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort - no constructor required."),(0,r.yg)("li",{parentName:"ul"},"For private or protected properties implemented, a public setter is required (if they are not set via the constructor). For example ",(0,r.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),". You can also put the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on the setter, instead of the property, allowing you to have use many other attributes (",(0,r.yg)("inlineCode",{parentName:"li"},"Security"),", ",(0,r.yg)("inlineCode",{parentName:"li"},"Right"),", ",(0,r.yg)("inlineCode",{parentName:"li"},"Autowire"),", etc.)."))),(0,r.yg)("li",{parentName:"ul"},"For validation of these Input Types, see the ",(0,r.yg)("a",{parentName:"li",href:"validation#custom-inputtype-validation"},"Custom InputType Validation section"),"."),(0,r.yg)("li",{parentName:"ul"},"It's advised to use the ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Input]")," attribute on DTO style input type objects and not directly on your model objects. Using it on your model objects can cause coupling in undesirable ways.")),(0,r.yg)("h3",{id:"multiple-input-types-from-the-same-class"},"Multiple Input Types from the same class"),(0,r.yg)("p",null,"Simple usage of the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Input"),' annotation on a class creates a GraphQL input named by class name + "Input" suffix if a class name does not end with it already. Ex. ',(0,r.yg)("inlineCode",{parentName:"p"},"LocationInput")," for ",(0,r.yg)("inlineCode",{parentName:"p"},"Location")," class."),(0,r.yg)("p",null,"You can add multiple ",(0,r.yg)("inlineCode",{parentName:"p"},"@Input")," annotations to the same class, give them different names and link different fields.\nConsider the following example:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n protected ?int $age;\n\n\n #[Field]\n public function setAge(?int $age): void\n {\n $this->age = $age;\n }\n}\n")),(0,r.yg)("p",null,"There are 2 input types added to the ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInput")," class: ",(0,r.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,r.yg)("li",{parentName:"ul"},"Field ",(0,r.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,r.yg)("li",{parentName:"ul"},"Field ",(0,r.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,r.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,r.yg)("li",{parentName:"ul"},"Field ",(0,r.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,r.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,r.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,r.yg)("li",{parentName:"ul"},"Field ",(0,r.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,r.yg)("p",null,"Note that ",(0,r.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,r.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,r.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,r.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."),(0,r.yg)("h2",{id:"factory"},"Factory"),(0,r.yg)("p",null,"A ",(0,r.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,r.yg)("p",null,"Here is an example of factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")),(0,r.yg)("p",null,"and now, you can run query like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,r.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,r.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,r.yg)("p",null,"A few important things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,r.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,r.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,r.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,r.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")),(0,r.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n")),(0,r.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,r.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,r.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,r.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\npublic function getProductById(#[UseInputType(inputType:"ID!")] string $id): Product {\n return $this->productRepository->findById($id);\n}\n')),(0,r.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,r.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,r.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,r.yg)("p",null,"Here is an annotated sample:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')),(0,r.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,r.yg)("p",null,"Image your ",(0,r.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n")),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,r.yg)("p",null,"To be able to hide an argument, the argument must have a default value."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/3f944aba.f7c92f88.js b/assets/js/3f944aba.676e3904.js similarity index 92% rename from assets/js/3f944aba.f7c92f88.js rename to assets/js/3f944aba.676e3904.js index f223cc3673..86bc9a16ef 100644 --- a/assets/js/3f944aba.f7c92f88.js +++ b/assets/js/3f944aba.676e3904.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3585],{23458:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-4.2/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-4.2/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/4.2/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/universal-service-providers.md",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"version-4.2/docs",previous:{title:"Laravel package",permalink:"/docs/4.2/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/4.2/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/4.2/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3585],{23458:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>p});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-4.2/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-4.2/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/4.2/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/universal-service-providers.md",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"version-4.2/docs",previous:{title:"Laravel package",permalink:"/docs/4.2/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/4.2/other-frameworks"}},l={},p=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:p},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/4.2/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/400ddbbb.394e55eb.js b/assets/js/400ddbbb.39b975fe.js similarity index 71% rename from assets/js/400ddbbb.394e55eb.js rename to assets/js/400ddbbb.39b975fe.js index 2da409ede0..89ec2bd649 100644 --- a/assets/js/400ddbbb.394e55eb.js +++ b/assets/js/400ddbbb.39b975fe.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6961],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const o={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),o=a(20053),u=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=p(e),[u,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,o]),tabValues:o}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,o.A)("tabs__item",v.tabItem,u?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function T(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},42342:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),o=(a(67443),a(11470)),u=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,i={unversionedId:"mutations",id:"version-6.0/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-6.0/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/6.0/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/mutations.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"docs",previous:{title:"Queries",permalink:"/docs/6.0/queries"},next:{title:"Type mapping",permalink:"/docs/6.0/type-mapping"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6961],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const u={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(u.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),u=a(20053),o=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(u),(0,r.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:u})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,u]),tabValues:u}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},o,{className:(0,u.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,u.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function T(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},42342:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),u=(a(67443),a(11470)),o=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,i={unversionedId:"mutations",id:"version-6.0/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-6.0/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/6.0/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/mutations.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"docs",previous:{title:"Queries",permalink:"/docs/6.0/queries"},next:{title:"Type mapping",permalink:"/docs/6.0/type-mapping"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4194805f.7648a706.js b/assets/js/4194805f.0ac84944.js similarity index 98% rename from assets/js/4194805f.7648a706.js rename to assets/js/4194805f.0ac84944.js index dede48497e..95e9020b82 100644 --- a/assets/js/4194805f.7648a706.js +++ b/assets/js/4194805f.0ac84944.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7116],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),i=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(n.location.search);t.set(i,e),n.replace({...n.location,search:t.toString()})}),[i,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,i]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:n}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,t)),r.createElement(b,(0,n.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},20142:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),i=(a(67443),a(11470)),o=a(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-7.0.0/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-7.0.0/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/autowiring.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"docs",previous:{title:"Type mapping",permalink:"/docs/type-mapping"},next:{title:"Extending a type",permalink:"/docs/extend-type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(h,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7116],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),i=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(n.location.search);t.set(i,e),n.replace({...n.location,search:t.toString()})}),[i,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,i]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:n}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,t)),r.createElement(b,(0,n.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},20142:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),i=(a(67443),a(11470)),o=a(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-7.0.0/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-7.0.0/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/autowiring.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"docs",previous:{title:"Type mapping",permalink:"/docs/type-mapping"},next:{title:"Extending a type",permalink:"/docs/extend-type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(h,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/471c3e37.0732a0f8.js b/assets/js/471c3e37.24278665.js similarity index 98% rename from assets/js/471c3e37.0732a0f8.js rename to assets/js/471c3e37.24278665.js index 814c41b21f..5b49d9b8c1 100644 --- a/assets/js/471c3e37.0732a0f8.js +++ b/assets/js/471c3e37.24278665.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8166],{9753:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>g,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var n=a(58168),r=(a(96540),a(15680));a(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/docs/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/next/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/argument-resolving.md",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"docs",previous:{title:"Custom attributes",permalink:"/docs/next/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/next/extend-input-type"}},s={},p=[{value:"Attributes parsing",id:"attributes-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},u="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(u,(0,n.A)({},m,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Autowire]")," attribute")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Validate]")," attribute (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"attributes-parsing"},"Attributes parsing"),(0,r.yg)("p",null,"If you plan to use attributes while resolving arguments, your attribute class should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Autowire]"),"."),(0,r.yg)("p",null,"We only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The class looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this attribute to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8166],{9753:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>g,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var n=a(58168),r=(a(96540),a(15680));a(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/docs/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/next/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/argument-resolving.md",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"docs",previous:{title:"Custom attributes",permalink:"/docs/next/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/next/extend-input-type"}},s={},p=[{value:"Attributes parsing",id:"attributes-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},u="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(u,(0,n.A)({},m,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Autowire]")," attribute")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Validate]")," attribute (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"attributes-parsing"},"Attributes parsing"),(0,r.yg)("p",null,"If you plan to use attributes while resolving arguments, your attribute class should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Autowire]"),"."),(0,r.yg)("p",null,"We only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The class looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this attribute to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/48fde361.9208d2e3.js b/assets/js/48fde361.4547b198.js similarity index 99% rename from assets/js/48fde361.9208d2e3.js rename to assets/js/48fde361.4547b198.js index bf65621fee..452b9239a3 100644 --- a/assets/js/48fde361.9208d2e3.js +++ b/assets/js/48fde361.4547b198.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[958],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var t=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),l=a(20053),i=a(23104),s=a(56347),p=a(57485),c=a(31682),o=a(89466);function u(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function m(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function d(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[p,c]=g({queryString:a,groupId:t}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=p??u;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),c(e),y(e)}),[c,y,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:p,tabValues:c}=e;const o=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=c[a].value;t!==s&&(u(n),p(t))},d=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:d,onClick:m},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function N(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(v,(0,t.A)({},e,n)))}function T(e){const n=(0,h.A)();return r.createElement(N,(0,t.A)({key:String(n)},e))}},64674:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>p,default:()=>g,frontMatter:()=>s,metadata:()=>c,toc:()=>u});var t=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const s={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},p=void 0,c={unversionedId:"inheritance-interfaces",id:"version-4.3/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-4.3/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/4.3/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/inheritance-interfaces.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"version-4.3/docs",previous:{title:"Input types",permalink:"/docs/4.3/input-types"},next:{title:"Error handling",permalink:"/docs/4.3/error-handling"}},o={},u=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],m={toc:u},d="wrapper";function g(e){let{components:n,...a}=e;return(0,r.yg)(d,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,r.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,r.yg)("p",null,"Let's say you have two classes, ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,r.yg)("p",null,"Written in ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,r.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,r.yg)("p",null,"The GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,r.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,r.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,r.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,r.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will translate in GraphQL schema as:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,r.yg)("p",null,"Please note that you do not need to put the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,r.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,r.yg)("p",null,"You don't have to explicitly put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")))),(0,r.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,r.yg)("p",null,"In the example above, because the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,r.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[958],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var t=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),l=a(20053),i=a(23104),s=a(56347),p=a(57485),c=a(31682),o=a(89466);function u(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function m(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function d(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[p,c]=g({queryString:a,groupId:t}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=p??u;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),c(e),y(e)}),[c,y,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:p,tabValues:c}=e;const o=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=c[a].value;t!==s&&(u(n),p(t))},d=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:d,onClick:m},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function N(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(v,(0,t.A)({},e,n)))}function T(e){const n=(0,h.A)();return r.createElement(N,(0,t.A)({key:String(n)},e))}},64674:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>p,default:()=>g,frontMatter:()=>s,metadata:()=>c,toc:()=>u});var t=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const s={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},p=void 0,c={unversionedId:"inheritance-interfaces",id:"version-4.3/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-4.3/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/4.3/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/inheritance-interfaces.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"version-4.3/docs",previous:{title:"Input types",permalink:"/docs/4.3/input-types"},next:{title:"Error handling",permalink:"/docs/4.3/error-handling"}},o={},u=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],m={toc:u},d="wrapper";function g(e){let{components:n,...a}=e;return(0,r.yg)(d,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,r.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,r.yg)("p",null,"Let's say you have two classes, ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,r.yg)("p",null,"Written in ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,r.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,r.yg)("p",null,"The GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,r.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,r.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,r.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,r.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will translate in GraphQL schema as:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,r.yg)("p",null,"Please note that you do not need to put the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,r.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,r.yg)("p",null,"You don't have to explicitly put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")))),(0,r.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,r.yg)("p",null,"In the example above, because the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,r.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4a060504.c4a87d87.js b/assets/js/4a060504.ea9289d9.js similarity index 98% rename from assets/js/4a060504.c4a87d87.js rename to assets/js/4a060504.ea9289d9.js index f6322b85e4..9f5839d0ce 100644 --- a/assets/js/4a060504.c4a87d87.js +++ b/assets/js/4a060504.ea9289d9.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8997],{69495:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating"},r=void 0,l={unversionedId:"migrating",id:"version-4.3/migrating",title:"Migrating",description:"Migrating from v4.0 to v4.1",source:"@site/versioned_docs/version-4.3/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/4.3/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/migrating.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating"},sidebar:"version-4.3/docs",previous:{title:"Troubleshooting",permalink:"/docs/4.3/troubleshooting"},next:{title:"Annotations VS Attributes",permalink:"/docs/4.3/doctrine-annotations-attributes"}},s={},g=[{value:"Migrating from v4.0 to v4.1",id:"migrating-from-v40-to-v41",level:2},{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},p="wrapper";function u(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v40-to-v41"},"Migrating from v4.0 to v4.1"),(0,i.yg)("p",null,"GraphQLite follows Semantic Versioning. GraphQLite 4.1 is backward compatible with GraphQLite 4.0. See\n",(0,i.yg)("a",{parentName:"p",href:"/docs/4.3/semver"},"semantic versioning")," for more details."),(0,i.yg)("p",null,"There is one exception though: the ",(0,i.yg)("strong",{parentName:"p"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL\ninput types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"p"},"composer.json")," by running this command:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/annotations-reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8997],{69495:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating"},r=void 0,l={unversionedId:"migrating",id:"version-4.3/migrating",title:"Migrating",description:"Migrating from v4.0 to v4.1",source:"@site/versioned_docs/version-4.3/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/4.3/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/migrating.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating"},sidebar:"version-4.3/docs",previous:{title:"Troubleshooting",permalink:"/docs/4.3/troubleshooting"},next:{title:"Annotations VS Attributes",permalink:"/docs/4.3/doctrine-annotations-attributes"}},s={},g=[{value:"Migrating from v4.0 to v4.1",id:"migrating-from-v40-to-v41",level:2},{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},p="wrapper";function u(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v40-to-v41"},"Migrating from v4.0 to v4.1"),(0,i.yg)("p",null,"GraphQLite follows Semantic Versioning. GraphQLite 4.1 is backward compatible with GraphQLite 4.0. See\n",(0,i.yg)("a",{parentName:"p",href:"/docs/4.3/semver"},"semantic versioning")," for more details."),(0,i.yg)("p",null,"There is one exception though: the ",(0,i.yg)("strong",{parentName:"p"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL\ninput types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"p"},"composer.json")," by running this command:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/4.3/annotations-reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4a07aaf0.e94a85e6.js b/assets/js/4a07aaf0.0965b827.js similarity index 98% rename from assets/js/4a07aaf0.e94a85e6.js rename to assets/js/4a07aaf0.0965b827.js index def3676c72..682822f860 100644 --- a/assets/js/4a07aaf0.e94a85e6.js +++ b/assets/js/4a07aaf0.0965b827.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8621],{19365:(e,t,n)=>{n.d(t,{A:()=>u});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:n,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),u=n(23104),o=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==o&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:u}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},u,{className:(0,l.A)("tabs__item",b.tabItem,u?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},14101:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),u=n(19365);const o={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-3.0/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-3.0/query_plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/3.0/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/query_plan.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8621],{19365:(e,t,n)=>{n.d(t,{A:()=>u});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:n,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),u=n(23104),o=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==o&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:u}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},u,{className:(0,l.A)("tabs__item",b.tabItem,u?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},14101:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),u=n(19365);const o={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-3.0/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-3.0/query_plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/3.0/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/query_plan.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4a2da18c.7c9b1ad0.js b/assets/js/4a2da18c.e7af6eba.js similarity index 98% rename from assets/js/4a2da18c.7c9b1ad0.js rename to assets/js/4a2da18c.e7af6eba.js index 5d3cec138b..9bce535088 100644 --- a/assets/js/4a2da18c.7c9b1ad0.js +++ b/assets/js/4a2da18c.e7af6eba.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6537],{34823:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var i=t(58168),a=(t(96540),t(15680));t(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},r=void 0,o={unversionedId:"field-middlewares",id:"version-5.0/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-5.0/field-middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/5.0/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/field-middlewares.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},sidebar:"version-5.0/docs",previous:{title:"Custom types",permalink:"/docs/5.0/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/5.0/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour of a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:t(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library (for PHP 7+) and/or by PHP 8 attributes."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="OnlyDebug.php"',title:'"OnlyDebug.php"'},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation is to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:"),(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"p"},"graphql.field_middleware")," tag."))))}c.isMDXComponent=!0},8643:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6537],{34823:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var i=t(58168),a=(t(96540),t(15680));t(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},r=void 0,o={unversionedId:"field-middlewares",id:"version-5.0/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-5.0/field-middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/5.0/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/field-middlewares.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},sidebar:"version-5.0/docs",previous:{title:"Custom types",permalink:"/docs/5.0/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/5.0/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour of a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:t(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library (for PHP 7+) and/or by PHP 8 attributes."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="OnlyDebug.php"',title:'"OnlyDebug.php"'},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation is to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:"),(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"p"},"graphql.field_middleware")," tag."))))}c.isMDXComponent=!0},8643:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file diff --git a/assets/js/4aab8b8c.f5a6f07e.js b/assets/js/4aab8b8c.8dc20bfd.js similarity index 98% rename from assets/js/4aab8b8c.f5a6f07e.js rename to assets/js/4aab8b8c.8dc20bfd.js index 340cbe7d6f..f107194ce4 100644 --- a/assets/js/4aab8b8c.f5a6f07e.js +++ b/assets/js/4aab8b8c.8dc20bfd.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3126],{81853:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>m});var t=a(58168),r=(a(96540),a(15680));a(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving",original_id:"argument-resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-4.0/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.0/argument_resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/4.0/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/argument_resolving.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving",original_id:"argument-resolving"},sidebar:"version-4.0/docs",previous:{title:"Custom annotations",permalink:"/docs/4.0/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/4.0/extend_input_type"}},s={},m=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],p={toc:m},g="wrapper";function d(e){let{components:n,...a}=e;return(0,r.yg)(g,(0,t.A)({},p,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," object. For instance:",(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"li"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"li",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware"))),(0,r.yg)("li",{parentName:"ul"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,r.yg)("li",{parentName:"ul"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Validate")," annotation (in Laravel package)")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middleware class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter resolver class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3126],{81853:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>m});var t=a(58168),r=(a(96540),a(15680));a(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving",original_id:"argument-resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-4.0/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.0/argument_resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/4.0/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/argument_resolving.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving",original_id:"argument-resolving"},sidebar:"version-4.0/docs",previous:{title:"Custom annotations",permalink:"/docs/4.0/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/4.0/extend_input_type"}},s={},m=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],p={toc:m},g="wrapper";function d(e){let{components:n,...a}=e;return(0,r.yg)(g,(0,t.A)({},p,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," object. For instance:",(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"li"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"li"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"li",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware"))),(0,r.yg)("li",{parentName:"ul"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,r.yg)("li",{parentName:"ul"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Validate")," annotation (in Laravel package)")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middleware class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter resolver class")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4bdafdff.ed49ea73.js b/assets/js/4bdafdff.f95fd3fd.js similarity index 99% rename from assets/js/4bdafdff.ed49ea73.js rename to assets/js/4bdafdff.f95fd3fd.js index 167daacf5e..4ebeb10adf 100644 --- a/assets/js/4bdafdff.ed49ea73.js +++ b/assets/js/4bdafdff.f95fd3fd.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[222],{27612:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>g,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>p,toc:()=>y});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,p={unversionedId:"annotations-reference",id:"version-6.0/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-6.0/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/6.0/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/annotations-reference.md",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/6.0/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/6.0/semver"}},g={},y=[{value:"@Query",id:"query",level:2},{value:"@Mutation",id:"mutation",level:2},{value:"@Type",id:"type",level:2},{value:"@ExtendType",id:"extendtype",level:2},{value:"@Input",id:"input",level:2},{value:"@Field",id:"field",level:2},{value:"@SourceField",id:"sourcefield",level:2},{value:"@MagicField",id:"magicfield",level:2},{value:"@Logged",id:"logged",level:2},{value:"@Right",id:"right",level:2},{value:"@FailWith",id:"failwith",level:2},{value:"@HideIfUnauthorized",id:"hideifunauthorized",level:2},{value:"@InjectUser",id:"injectuser",level:2},{value:"@Security",id:"security",level:2},{value:"@Factory",id:"factory",level:2},{value:"@UseInputType",id:"useinputtype",level:2},{value:"@Decorate",id:"decorate",level:2},{value:"@Autowire",id:"autowire",level:2},{value:"@HideParameter",id:"hideparameter",level:2},{value:"@Validate",id:"validate",level:2},{value:"@Assertion",id:"assertion",level:2},{value:"@EnumType",id:"enumtype",level:2}],o={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},o,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query"},"@Query"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation"},"@Mutation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type"},"@Type"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type. This is used with standard output\ntypes, as well as enum types. For input types, use the ",(0,l.yg)("a",{parentName:"p",href:"#input-annotation"},"@Input annotation")," directly on the input type or a ",(0,l.yg)("a",{parentName:"p",href:"#factory-annotation"},"@Factory annoation")," to make/return an input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class/enum for the actual type. If no "class" attribute is passed, the type applies to the current class/enum. The current class/enum is assumed to be an entity (not service). If the "class" attribute ',(0,l.yg)("em",{parentName:"td"},"is passed"),", ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/external-type-declaration"},"the class/enum annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," becomes a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype"},"@ExtendType"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input"},"@Input"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Name of the input type represented in your GraphQL schema. Defaults to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," ",(0,l.yg)("em",{parentName:"td"},"only if")," the name is not specified. If ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is specified, this will default to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", so must also be included for ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," when ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation. This primarily applies to nullable fields.")))),(0,l.yg)("h2",{id:"field"},"@Field"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield"},"@SourceField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield"},"@MagicField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If not set, no description will be shown.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged"},"@Logged"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right"},"@Right"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith"},"@FailWith"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized"},"@HideIfUnauthorized"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser"},"@InjectUser"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security"},"@Security"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory"},"@Factory"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype"},"@UseInputType"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate"},"@Decorate"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire"},"@Autowire"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter"},"@HideParameter"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate"},"@Validate"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion"},"@Assertion"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype"},(0,l.yg)("del",{parentName:"h2"},"@EnumType")),(0,l.yg)("p",null,(0,l.yg)("em",{parentName:"p"},"Deprecated: Use ",(0,l.yg)("a",{parentName:"em",href:"https://www.php.net/manual/en/language.types.enumerations.php"},"PHP 8.1's native Enums")," instead with a ",(0,l.yg)("a",{parentName:"em",href:"#type-annotation"},"@Type"),".")),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[222],{27612:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>g,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>p,toc:()=>y});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,p={unversionedId:"annotations-reference",id:"version-6.0/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-6.0/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/6.0/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/annotations-reference.md",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/6.0/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/6.0/semver"}},g={},y=[{value:"@Query",id:"query",level:2},{value:"@Mutation",id:"mutation",level:2},{value:"@Type",id:"type",level:2},{value:"@ExtendType",id:"extendtype",level:2},{value:"@Input",id:"input",level:2},{value:"@Field",id:"field",level:2},{value:"@SourceField",id:"sourcefield",level:2},{value:"@MagicField",id:"magicfield",level:2},{value:"@Logged",id:"logged",level:2},{value:"@Right",id:"right",level:2},{value:"@FailWith",id:"failwith",level:2},{value:"@HideIfUnauthorized",id:"hideifunauthorized",level:2},{value:"@InjectUser",id:"injectuser",level:2},{value:"@Security",id:"security",level:2},{value:"@Factory",id:"factory",level:2},{value:"@UseInputType",id:"useinputtype",level:2},{value:"@Decorate",id:"decorate",level:2},{value:"@Autowire",id:"autowire",level:2},{value:"@HideParameter",id:"hideparameter",level:2},{value:"@Validate",id:"validate",level:2},{value:"@Assertion",id:"assertion",level:2},{value:"@EnumType",id:"enumtype",level:2}],o={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},o,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query"},"@Query"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation"},"@Mutation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type"},"@Type"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type. This is used with standard output\ntypes, as well as enum types. For input types, use the ",(0,l.yg)("a",{parentName:"p",href:"#input-annotation"},"@Input annotation")," directly on the input type or a ",(0,l.yg)("a",{parentName:"p",href:"#factory-annotation"},"@Factory annoation")," to make/return an input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class/enum for the actual type. If no "class" attribute is passed, the type applies to the current class/enum. The current class/enum is assumed to be an entity (not service). If the "class" attribute ',(0,l.yg)("em",{parentName:"td"},"is passed"),", ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/external-type-declaration"},"the class/enum annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," becomes a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype"},"@ExtendType"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input"},"@Input"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Name of the input type represented in your GraphQL schema. Defaults to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," ",(0,l.yg)("em",{parentName:"td"},"only if")," the name is not specified. If ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is specified, this will default to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", so must also be included for ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," when ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation. This primarily applies to nullable fields.")))),(0,l.yg)("h2",{id:"field"},"@Field"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield"},"@SourceField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield"},"@MagicField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If not set, no description will be shown.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged"},"@Logged"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right"},"@Right"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith"},"@FailWith"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized"},"@HideIfUnauthorized"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser"},"@InjectUser"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security"},"@Security"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory"},"@Factory"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.0/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype"},"@UseInputType"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate"},"@Decorate"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire"},"@Autowire"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter"},"@HideParameter"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate"},"@Validate"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion"},"@Assertion"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype"},(0,l.yg)("del",{parentName:"h2"},"@EnumType")),(0,l.yg)("p",null,(0,l.yg)("em",{parentName:"p"},"Deprecated: Use ",(0,l.yg)("a",{parentName:"em",href:"https://www.php.net/manual/en/language.types.enumerations.php"},"PHP 8.1's native Enums")," instead with a ",(0,l.yg)("a",{parentName:"em",href:"#type-annotation"},"@Type"),".")),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4c5bf49d.ca1a6b73.js b/assets/js/4c5bf49d.532665cc.js similarity index 70% rename from assets/js/4c5bf49d.ca1a6b73.js rename to assets/js/4c5bf49d.532665cc.js index ac28f08e48..a2b2ffa909 100644 --- a/assets/js/4c5bf49d.ca1a6b73.js +++ b/assets/js/4c5bf49d.532665cc.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2720],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const o={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>w});var n=a(58168),r=a(96540),o=a(20053),u=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=p(e),[u,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,o]),tabValues:o}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,o.A)("tabs__item",v.tabItem,u?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function w(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},51969:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),o=(a(67443),a(11470)),u=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,i={unversionedId:"mutations",id:"version-7.0.0/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-7.0.0/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/mutations.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"docs",previous:{title:"Queries",permalink:"/docs/queries"},next:{title:"Subscriptions",permalink:"/docs/subscriptions"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2720],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const u={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(u.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>w});var n=a(58168),r=a(96540),u=a(20053),o=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(u),(0,r.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:u})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,u]),tabValues:u}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},o,{className:(0,u.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,u.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function w(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},51969:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),u=(a(67443),a(11470)),o=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,i={unversionedId:"mutations",id:"version-7.0.0/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-7.0.0/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/mutations.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"docs",previous:{title:"Queries",permalink:"/docs/queries"},next:{title:"Subscriptions",permalink:"/docs/subscriptions"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4c7f7507.0cf12320.js b/assets/js/4c7f7507.727d59a5.js similarity index 99% rename from assets/js/4c7f7507.0cf12320.js rename to assets/js/4c7f7507.727d59a5.js index 20e6061f07..e7d515bf24 100644 --- a/assets/js/4c7f7507.0cf12320.js +++ b/assets/js/4c7f7507.727d59a5.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9472],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var t=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),l=a(20053),i=a(23104),s=a(56347),p=a(57485),c=a(31682),o=a(89466);function u(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function m(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function d(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function h(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[p,c]=g({queryString:a,groupId:t}),[u,h]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),y=(()=>{const e=p??u;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&s(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),c(e),h(e)}),[c,h,l]),tabValues:l}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:p,tabValues:c}=e;const o=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=c[a].value;t!==s&&(u(n),p(t))},d=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:d,onClick:m},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function N(e){const n=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(v,(0,t.A)({},e,n)))}function T(e){const n=(0,y.A)();return r.createElement(N,(0,t.A)({key:String(n)},e))}},34844:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>p,default:()=>g,frontMatter:()=>s,metadata:()=>c,toc:()=>u});var t=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const s={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance-interfaces"},p=void 0,c={unversionedId:"inheritance-interfaces",id:"version-4.1/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-4.1/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/4.1/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/inheritance-interfaces.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance-interfaces"},sidebar:"version-4.1/docs",previous:{title:"Input types",permalink:"/docs/4.1/input-types"},next:{title:"Error handling",permalink:"/docs/4.1/error-handling"}},o={},u=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],m={toc:u},d="wrapper";function g(e){let{components:n,...a}=e;return(0,r.yg)(d,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,r.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,r.yg)("p",null,"Let's say you have two classes, ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,r.yg)("p",null,"Written in ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,r.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,r.yg)("p",null,"The GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,r.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,r.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,r.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,r.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will translate in GraphQL schema as:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,r.yg)("p",null,"Please note that you do not need to put the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,r.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,r.yg)("p",null,"You don't have to explicitly put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")))),(0,r.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,r.yg)("p",null,"In the example above, because the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,r.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9472],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var t=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),l=a(20053),i=a(23104),s=a(56347),p=a(57485),c=a(31682),o=a(89466);function u(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function m(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function d(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function h(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[p,c]=g({queryString:a,groupId:t}),[u,h]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),y=(()=>{const e=p??u;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&s(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),c(e),h(e)}),[c,h,l]),tabValues:l}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:p,tabValues:c}=e;const o=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=c[a].value;t!==s&&(u(n),p(t))},d=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:d,onClick:m},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function N(e){const n=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(v,(0,t.A)({},e,n)))}function T(e){const n=(0,y.A)();return r.createElement(N,(0,t.A)({key:String(n)},e))}},34844:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>p,default:()=>g,frontMatter:()=>s,metadata:()=>c,toc:()=>u});var t=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const s={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance-interfaces"},p=void 0,c={unversionedId:"inheritance-interfaces",id:"version-4.1/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-4.1/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/4.1/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/inheritance-interfaces.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance-interfaces"},sidebar:"version-4.1/docs",previous:{title:"Input types",permalink:"/docs/4.1/input-types"},next:{title:"Error handling",permalink:"/docs/4.1/error-handling"}},o={},u=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],m={toc:u},d="wrapper";function g(e){let{components:n,...a}=e;return(0,r.yg)(d,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,r.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,r.yg)("p",null,"Let's say you have two classes, ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,r.yg)("p",null,"Written in ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,r.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,r.yg)("p",null,"The GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,r.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,r.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,r.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,r.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will translate in GraphQL schema as:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,r.yg)("p",null,"Please note that you do not need to put the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,r.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,r.yg)("p",null,"You don't have to explicitly put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")))),(0,r.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,r.yg)("p",null,"In the example above, because the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,r.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4d00e03a.c44f7319.js b/assets/js/4d00e03a.c8d322b7.js similarity index 98% rename from assets/js/4d00e03a.c44f7319.js rename to assets/js/4d00e03a.c8d322b7.js index ccc5d1848f..2f013272bf 100644 --- a/assets/js/4d00e03a.c44f7319.js +++ b/assets/js/4d00e03a.c8d322b7.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8688],{36428:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>r,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>d});var t=n(58168),i=(n(96540),n(15680));n(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"version-4.2/changelog",title:"Changelog",description:"4.2.0",source:"@site/versioned_docs/version-4.2/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/4.2/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/CHANGELOG.md",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"version-4.2/docs",previous:{title:"Semantic versioning",permalink:"/docs/4.2/semver"}},p={},d=[{value:"4.2.0",id:"420",level:2},{value:"Breaking change:",id:"breaking-change",level:4},{value:"New features:",id:"new-features",level:4},{value:"4.1.0",id:"410",level:2},{value:"Breaking change:",id:"breaking-change-1",level:4},{value:"New features:",id:"new-features-1",level:4},{value:"Minor changes:",id:"minor-changes",level:4},{value:"Miscellaneous:",id:"miscellaneous",level:4},{value:"4.0.0",id:"400",level:2},{value:"New features:",id:"new-features-2",level:4},{value:"Symfony:",id:"symfony",level:4},{value:"Laravel:",id:"laravel",level:4},{value:"Internals:",id:"internals",level:4}],s={toc:d},u="wrapper";function g(e){let{components:a,...n}=e;return(0,i.yg)(u,(0,t.A)({},s,n,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h4",{id:"breaking-change"},"Breaking change:"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h4",{id:"new-features"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"@Type")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/input-types#input-annotation"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Logged"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h4",{id:"breaking-change-1"},"Breaking change:"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h4",{id:"new-features-1"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h4",{id:"minor-changes"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h4",{id:"miscellaneous"},"Miscellaneous:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h4",{id:"new-features-2"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h4",{id:"symfony"},"Symfony:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h4",{id:"laravel"},"Laravel:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h4",{id:"internals"},"Internals:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8688],{36428:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>r,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>d});var t=n(58168),i=(n(96540),n(15680));n(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"version-4.2/changelog",title:"Changelog",description:"4.2.0",source:"@site/versioned_docs/version-4.2/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/4.2/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/CHANGELOG.md",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"version-4.2/docs",previous:{title:"Semantic versioning",permalink:"/docs/4.2/semver"}},p={},d=[{value:"4.2.0",id:"420",level:2},{value:"Breaking change:",id:"breaking-change",level:4},{value:"New features:",id:"new-features",level:4},{value:"4.1.0",id:"410",level:2},{value:"Breaking change:",id:"breaking-change-1",level:4},{value:"New features:",id:"new-features-1",level:4},{value:"Minor changes:",id:"minor-changes",level:4},{value:"Miscellaneous:",id:"miscellaneous",level:4},{value:"4.0.0",id:"400",level:2},{value:"New features:",id:"new-features-2",level:4},{value:"Symfony:",id:"symfony",level:4},{value:"Laravel:",id:"laravel",level:4},{value:"Internals:",id:"internals",level:4}],s={toc:d},u="wrapper";function g(e){let{components:a,...n}=e;return(0,i.yg)(u,(0,t.A)({},s,n,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h4",{id:"breaking-change"},"Breaking change:"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h4",{id:"new-features"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"@Type")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/input-types#input-annotation"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Logged"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h4",{id:"breaking-change-1"},"Breaking change:"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h4",{id:"new-features-1"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h4",{id:"minor-changes"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h4",{id:"miscellaneous"},"Miscellaneous:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h4",{id:"new-features-2"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h4",{id:"symfony"},"Symfony:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h4",{id:"laravel"},"Laravel:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.2/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h4",{id:"internals"},"Internals:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4d049718.a2a7e562.js b/assets/js/4d049718.6b397ac8.js similarity index 99% rename from assets/js/4d049718.a2a7e562.js rename to assets/js/4d049718.6b397ac8.js index 4add26b45f..b89f5938c7 100644 --- a/assets/js/4d049718.a2a7e562.js +++ b/assets/js/4d049718.6b397ac8.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2156],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),o=t(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),o=t(96540),i=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:o}}=e;return{value:n,label:t,attributes:a,default:o}}))}function d(e){const{values:n,children:t}=e;return(0,o.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,o.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,o.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,n)),o.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return o.createElement(v,(0,a.A)({key:String(n)},e))}},38556:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),o=(t(96540),t(15680)),i=(t(67443),t(11470)),r=t(19365);const l={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},u=void 0,s={unversionedId:"external-type-declaration",id:"version-5.0/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-5.0/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/5.0/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/external-type-declaration.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"version-5.0/docs",previous:{title:"Extending a type",permalink:"/docs/5.0/extend-type"},next:{title:"Input types",permalink:"/docs/5.0/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,o.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),")."),(0,o.yg)("p",null,"You can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"You can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/5.0/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/5.0/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2156],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),o=t(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),o=t(96540),i=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:o}}=e;return{value:n,label:t,attributes:a,default:o}}))}function d(e){const{values:n,children:t}=e;return(0,o.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,o.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,o.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,n)),o.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return o.createElement(v,(0,a.A)({key:String(n)},e))}},38556:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),o=(t(96540),t(15680)),i=(t(67443),t(11470)),r=t(19365);const l={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},u=void 0,s={unversionedId:"external-type-declaration",id:"version-5.0/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-5.0/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/5.0/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/external-type-declaration.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"version-5.0/docs",previous:{title:"Extending a type",permalink:"/docs/5.0/extend-type"},next:{title:"Input types",permalink:"/docs/5.0/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,o.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),")."),(0,o.yg)("p",null,"You can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"You can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/5.0/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/5.0/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4d68b066.5010896b.js b/assets/js/4d68b066.da01e2ba.js similarity index 92% rename from assets/js/4d68b066.5010896b.js rename to assets/js/4d68b066.da01e2ba.js index 40df3ce50f..045354a3cc 100644 --- a/assets/js/4d68b066.5010896b.js +++ b/assets/js/4d68b066.da01e2ba.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1443],{35927:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning",original_id:"semver"},o=void 0,s={unversionedId:"semver",id:"version-4.1/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all",source:"@site/versioned_docs/version-4.1/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/4.1/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/semver.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning",original_id:"semver"},sidebar:"version-4.1/docs",previous:{title:"Annotations reference",permalink:"/docs/4.1/annotations_reference"},next:{title:"Changelog",permalink:"/docs/4.1/changelog"}},l={},p=[],m={toc:p},d="wrapper";function u(e){let{components:a,...t}=e;return(0,n.yg)(d,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all\nminor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short,\nSemantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility.\nMinor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of\nthat release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental"),"\nand their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a major version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a minor version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library.\nThey are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1443],{35927:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning",original_id:"semver"},o=void 0,s={unversionedId:"semver",id:"version-4.1/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all",source:"@site/versioned_docs/version-4.1/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/4.1/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/semver.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning",original_id:"semver"},sidebar:"version-4.1/docs",previous:{title:"Annotations reference",permalink:"/docs/4.1/annotations_reference"},next:{title:"Changelog",permalink:"/docs/4.1/changelog"}},l={},p=[],m={toc:p},u="wrapper";function d(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all\nminor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short,\nSemantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility.\nMinor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of\nthat release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental"),"\nand their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a major version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a minor version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library.\nThey are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4dd5816e.b4e531cf.js b/assets/js/4dd5816e.54289d64.js similarity index 99% rename from assets/js/4dd5816e.b4e531cf.js rename to assets/js/4dd5816e.54289d64.js index 2e0a6c65b7..087916ff3b 100644 --- a/assets/js/4dd5816e.b4e531cf.js +++ b/assets/js/4dd5816e.54289d64.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1604],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),l=t(20053),o=t(23104),i=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,i.W6)(),l=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(n.location.search);a.set(l,e),n.replace({...n.location,search:a.toString()})}),[l,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,l=g(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:l}))),[s,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,l]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:n}),m=(()=>{const e=s??c;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==i&&(c(a),s(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===a?0:-1,"aria-selected":i===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},70758:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),o=t(19365);const i={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},s=void 0,u={unversionedId:"fine-grained-security",id:"version-6.0/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-6.0/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/6.0/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/fine-grained-security.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},sidebar:"docs",previous:{title:"Authentication and authorization",permalink:"/docs/6.0/authentication-authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/6.0/implementing-security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/authentication-authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/authentication-authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1604],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),l=t(20053),o=t(23104),i=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,i.W6)(),l=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(n.location.search);a.set(l,e),n.replace({...n.location,search:a.toString()})}),[l,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,l=g(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:l}))),[s,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,l]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:n}),m=(()=>{const e=s??c;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==i&&(c(a),s(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===a?0:-1,"aria-selected":i===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},70758:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),o=t(19365);const i={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},s=void 0,u={unversionedId:"fine-grained-security",id:"version-6.0/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-6.0/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/6.0/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/fine-grained-security.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},sidebar:"docs",previous:{title:"Authentication and authorization",permalink:"/docs/6.0/authentication-authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/6.0/implementing-security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/authentication-authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/authentication-authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4dfeb783.57f6daf2.js b/assets/js/4dfeb783.4e4a50df.js similarity index 99% rename from assets/js/4dfeb783.57f6daf2.js rename to assets/js/4dfeb783.4e4a50df.js index 6d45bda5e3..2e98d4d580 100644 --- a/assets/js/4dfeb783.57f6daf2.js +++ b/assets/js/4dfeb783.4e4a50df.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1126],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),o=a(57485),p=a(31682),s=a(89466);function d(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function c(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,o.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=c(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[o,p]=m({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=o??d;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),p(e),h(e)}),[p,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:o,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=s.indexOf(t),n=p[a].value;n!==i&&(d(t),o(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},p.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:c},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},10149:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>i,metadata:()=>p,toc:()=>d});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},o=void 0,p={unversionedId:"multiple-output-types",id:"version-4.3/multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.3/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/4.3/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/multiple-output-types.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"version-4.3/docs",previous:{title:"Extending an input type",permalink:"/docs/4.3/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/4.3/symfony-bundle-advanced"}},s={},d=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],c={toc:d},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/external-type-declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1126],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),o=a(57485),p=a(31682),s=a(89466);function d(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function c(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,o.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=c(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[o,p]=m({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=o??d;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),p(e),h(e)}),[p,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:o,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=s.indexOf(t),n=p[a].value;n!==i&&(d(t),o(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},p.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:c},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},10149:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>i,metadata:()=>p,toc:()=>d});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},o=void 0,p={unversionedId:"multiple-output-types",id:"version-4.3/multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.3/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/4.3/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/multiple-output-types.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"version-4.3/docs",previous:{title:"Extending an input type",permalink:"/docs/4.3/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/4.3/symfony-bundle-advanced"}},s={},d=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],c={toc:d},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.3/external-type-declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4e1a0951.011df4f3.js b/assets/js/4e1a0951.a596bbff.js similarity index 99% rename from assets/js/4e1a0951.011df4f3.js rename to assets/js/4e1a0951.a596bbff.js index f9fe0e8f0f..cc59ab8e63 100644 --- a/assets/js/4e1a0951.011df4f3.js +++ b/assets/js/4e1a0951.a596bbff.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4843],{19365:(e,a,n)=>{n.d(a,{A:()=>i});var t=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:n,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},a)}},11470:(e,a,n)=>{n.d(a,{A:()=>P});var t=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:n,attributes:t,default:r}}=e;return{value:a,label:n,attributes:t,default:r}}))}function d(e){const{values:a,children:n}=e;return(0,r.useMemo)((()=>{const e=a??c(n);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,n])}function g(e){let{value:a,tabValues:n}=e;return n.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:n}=e;const t=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:n}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:a,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(t.location.search);a.set(l,e),t.replace({...t.location,search:a.toString()})}),[l,t])]}function m(e){const{defaultValue:a,queryString:n=!1,groupId:t}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const t=n.find((e=>e.default))??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:n,groupId:t}),[c,m]=function(e){let{groupId:a}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(a),[t,l]=(0,p.Dv)(n);return[t,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:t}),y=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const a=e.currentTarget,n=p.indexOf(a),t=u[n].value;t!==o&&(c(a),s(t))},g=e=>{let a=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;a=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;a=p[n]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},a)},u.map((e=>{let{value:a,label:n,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),n??a)})))}function b(e){let{lazy:a,children:n,selectedValue:t}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==t}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,t.A)({},e,a)),r.createElement(b,(0,t.A)({},e,a)))}function P(e){const a=(0,y.A)();return r.createElement(w,(0,t.A)({key:String(a)},e))}},46224:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var t=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-3.0/laravel-package-advanced",title:"Laravel package: advanced usage",description:"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel.",source:"@site/versioned_docs/version-3.0/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/3.0/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/laravel-package-advanced.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"}},p={},c=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3}],d={toc:c},g="wrapper";function h(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"```php\nclass User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n```\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'```php\n/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n```\n'))))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4843],{19365:(e,a,n)=>{n.d(a,{A:()=>i});var t=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:n,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},a)}},11470:(e,a,n)=>{n.d(a,{A:()=>P});var t=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:n,attributes:t,default:r}}=e;return{value:a,label:n,attributes:t,default:r}}))}function d(e){const{values:a,children:n}=e;return(0,r.useMemo)((()=>{const e=a??c(n);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,n])}function g(e){let{value:a,tabValues:n}=e;return n.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:n}=e;const t=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:n}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:a,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(t.location.search);a.set(l,e),t.replace({...t.location,search:a.toString()})}),[l,t])]}function m(e){const{defaultValue:a,queryString:n=!1,groupId:t}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const t=n.find((e=>e.default))??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:n,groupId:t}),[c,m]=function(e){let{groupId:a}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(a),[t,l]=(0,p.Dv)(n);return[t,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:t}),y=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const a=e.currentTarget,n=p.indexOf(a),t=u[n].value;t!==o&&(c(a),s(t))},g=e=>{let a=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;a=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;a=p[n]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},a)},u.map((e=>{let{value:a,label:n,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),n??a)})))}function b(e){let{lazy:a,children:n,selectedValue:t}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==t}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,t.A)({},e,a)),r.createElement(b,(0,t.A)({},e,a)))}function P(e){const a=(0,y.A)();return r.createElement(w,(0,t.A)({key:String(a)},e))}},46224:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var t=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-3.0/laravel-package-advanced",title:"Laravel package: advanced usage",description:"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel.",source:"@site/versioned_docs/version-3.0/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/3.0/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/laravel-package-advanced.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"}},p={},c=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3}],d={toc:c},g="wrapper";function h(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"```php\nclass User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n```\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'```php\n/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n```\n'))))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4e73bd72.50ba6eee.js b/assets/js/4e73bd72.5ec85a48.js similarity index 80% rename from assets/js/4e73bd72.50ba6eee.js rename to assets/js/4e73bd72.5ec85a48.js index 1f75aa723a..c018bb55ec 100644 --- a/assets/js/4e73bd72.50ba6eee.js +++ b/assets/js/4e73bd72.5ec85a48.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4981],{34027:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>u,contentTitle:()=>s,default:()=>c,frontMatter:()=>o,metadata:()=>r,toc:()=>d});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},s=void 0,r={unversionedId:"mutations",id:"version-3.0/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-3.0/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/3.0/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/mutations.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},sidebar:"version-3.0/docs",previous:{title:"Queries",permalink:"/docs/3.0/queries"},next:{title:"Type mapping",permalink:"/docs/3.0/type_mapping"}},u={},d=[],l={toc:d},p="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(p,(0,a.A)({},l,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In GraphQLite, mutations are created ",(0,i.yg)("a",{parentName:"p",href:"/docs/3.0/queries"},"like queries"),"."),(0,i.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4981],{34027:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>u,contentTitle:()=>s,default:()=>c,frontMatter:()=>o,metadata:()=>r,toc:()=>l});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},s=void 0,r={unversionedId:"mutations",id:"version-3.0/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-3.0/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/3.0/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/mutations.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},sidebar:"version-3.0/docs",previous:{title:"Queries",permalink:"/docs/3.0/queries"},next:{title:"Type mapping",permalink:"/docs/3.0/type_mapping"}},u={},l=[],d={toc:l},p="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(p,(0,a.A)({},d,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In GraphQLite, mutations are created ",(0,i.yg)("a",{parentName:"p",href:"/docs/3.0/queries"},"like queries"),"."),(0,i.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4f30448a.ad33b3b8.js b/assets/js/4f30448a.18e6f579.js similarity index 99% rename from assets/js/4f30448a.ad33b3b8.js rename to assets/js/4f30448a.18e6f579.js index df8cedf814..f84c6a973a 100644 --- a/assets/js/4f30448a.ad33b3b8.js +++ b/assets/js/4f30448a.18e6f579.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9581],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>v});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function w(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function v(e){const n=(0,m.A)();return a.createElement(w,(0,r.A)({key:String(n)},e))}},39077:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-3.0/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-3.0/error_handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/3.0/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/error_handling.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/3.0/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/3.0/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9581],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>v});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function w(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function v(e){const n=(0,m.A)();return a.createElement(w,(0,r.A)({key:String(n)},e))}},39077:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-3.0/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-3.0/error_handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/3.0/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/error_handling.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/3.0/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/3.0/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4f4b6633.29f9fc0d.js b/assets/js/4f4b6633.13723156.js similarity index 99% rename from assets/js/4f4b6633.29f9fc0d.js rename to assets/js/4f4b6633.13723156.js index 03c0142bab..c664f43157 100644 --- a/assets/js/4f4b6633.29f9fc0d.js +++ b/assets/js/4f4b6633.13723156.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2589],{19365:(e,n,a)=>{a.d(n,{A:()=>r});var t=a(96540),p=a(20053);const l={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:a,className:r}=e;return t.createElement("div",{role:"tabpanel",className:(0,p.A)(l.tabItem,r),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>N});var t=a(58168),p=a(96540),l=a(20053),r=a(23104),s=a(56347),i=a(57485),u=a(31682),o=a(89466);function c(e){return function(e){return p.Children.map(e,(e=>{if(!e||(0,p.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:p}}=e;return{value:n,label:a,attributes:t,default:p}}))}function y(e){const{values:n,children:a}=e;return(0,p.useMemo)((()=>{const e=n??c(a);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function m(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function d(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,i.aZ)(l),(0,p.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function g(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=y(e),[r,s]=(0,p.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[i,u]=d({queryString:a,groupId:t}),[c,g]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,p.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=i??c;return m({value:e,tabValues:l})?e:null})();(0,p.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:r,selectValue:(0,p.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const o=[],{blockElementScrollPositionUntilNextRender:c}=(0,r.a_)(),y=e=>{const n=e.currentTarget,a=o.indexOf(n),t=u[a].value;t!==s&&(c(n),i(t))},m=e=>{let n=null;switch(e.key){case"Enter":y(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return p.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},u.map((e=>{let{value:n,label:a,attributes:r}=e;return p.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:m,onClick:y},r,{className:(0,l.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,p.cloneElement)(e,{className:"margin-top--md"}):null}return p.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,p.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function T(e){const n=g(e);return p.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},p.createElement(b,(0,t.A)({},e,n)),p.createElement(v,(0,t.A)({},e,n)))}function N(e){const n=(0,h.A)();return p.createElement(T,(0,t.A)({key:String(n)},e))}},64316:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>u,toc:()=>c});var t=a(58168),p=(a(96540),a(15680)),l=(a(67443),a(11470)),r=a(19365);const s={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},i=void 0,u={unversionedId:"type-mapping",id:"version-5.0/type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-5.0/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/5.0/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/type-mapping.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"version-5.0/docs",previous:{title:"Mutations",permalink:"/docs/5.0/mutations"},next:{title:"Autowiring services",permalink:"/docs/5.0/autowiring"}},o={},c=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],y={toc:c},m="wrapper";function d(e){let{components:n,...a}=e;return(0,p.yg)(m,(0,t.A)({},y,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/5.0/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(name="MyProduct")\n */\nclass Product { /* ... */ }\n')))),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')))),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')))),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/5.0/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")))),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")))),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")))),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"You can create a GraphQL union type ",(0,p.yg)("em",{parentName:"p"},"on the fly")," using the pipe ",(0,p.yg)("inlineCode",{parentName:"p"},"|")," operator in the PHPDoc:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Company|Contact <== can return a company OR a contact.\n */\n#[Query]\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact <== can return a company OR a contact.\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")))),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,"PHP has no native support for enum types. Hopefully, there are a number of PHP libraries that emulate enums in PHP.\nThe most commonly used library is ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum")," and GraphQLite comes with\nnative support for it."),(0,p.yg)("p",null,"You will first need to install ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[]\n */\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')))),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n/**\n * @EnumType(name="UserStatus")\n */\nclass StatusEnum extends Enum\n{\n // ...\n}\n')))),(0,p.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,p.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,p.yg)("div",{class:"alert alert--info"},'There are many enumeration library in PHP and you might be using another library. If you want to add support for your own library, this is not extremely difficult to do. You need to register a custom "RootTypeMapper" with GraphQLite. You can learn more about ',(0,p.yg)("em",null,"type mappers")," in the ",(0,p.yg)("a",{href:"internals"},'"internals" documentation'),"and ",(0,p.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/MyCLabsEnumTypeMapper.php"},"copy/paste and adapt the root type mapper used for myclabs/php-enum"),"."),(0,p.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,p.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n * @deprecated use field `name` instead\n */\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,p.yg)("p",null,"This will add the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,p.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,p.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,p.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,p.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,p.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2589],{19365:(e,n,a)=>{a.d(n,{A:()=>r});var t=a(96540),p=a(20053);const l={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:a,className:r}=e;return t.createElement("div",{role:"tabpanel",className:(0,p.A)(l.tabItem,r),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>N});var t=a(58168),p=a(96540),l=a(20053),r=a(23104),s=a(56347),i=a(57485),u=a(31682),o=a(89466);function c(e){return function(e){return p.Children.map(e,(e=>{if(!e||(0,p.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:p}}=e;return{value:n,label:a,attributes:t,default:p}}))}function y(e){const{values:n,children:a}=e;return(0,p.useMemo)((()=>{const e=n??c(a);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function m(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function d(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,i.aZ)(l),(0,p.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function g(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=y(e),[r,s]=(0,p.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[i,u]=d({queryString:a,groupId:t}),[c,g]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,p.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=i??c;return m({value:e,tabValues:l})?e:null})();(0,p.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:r,selectValue:(0,p.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const o=[],{blockElementScrollPositionUntilNextRender:c}=(0,r.a_)(),y=e=>{const n=e.currentTarget,a=o.indexOf(n),t=u[a].value;t!==s&&(c(n),i(t))},m=e=>{let n=null;switch(e.key){case"Enter":y(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return p.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},u.map((e=>{let{value:n,label:a,attributes:r}=e;return p.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:m,onClick:y},r,{className:(0,l.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,p.cloneElement)(e,{className:"margin-top--md"}):null}return p.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,p.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function T(e){const n=g(e);return p.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},p.createElement(b,(0,t.A)({},e,n)),p.createElement(v,(0,t.A)({},e,n)))}function N(e){const n=(0,h.A)();return p.createElement(T,(0,t.A)({key:String(n)},e))}},64316:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>u,toc:()=>c});var t=a(58168),p=(a(96540),a(15680)),l=(a(67443),a(11470)),r=a(19365);const s={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},i=void 0,u={unversionedId:"type-mapping",id:"version-5.0/type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-5.0/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/5.0/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/type-mapping.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"version-5.0/docs",previous:{title:"Mutations",permalink:"/docs/5.0/mutations"},next:{title:"Autowiring services",permalink:"/docs/5.0/autowiring"}},o={},c=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],y={toc:c},m="wrapper";function d(e){let{components:n,...a}=e;return(0,p.yg)(m,(0,t.A)({},y,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/5.0/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(name="MyProduct")\n */\nclass Product { /* ... */ }\n')))),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')))),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')))),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/5.0/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")))),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")))),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")))),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"You can create a GraphQL union type ",(0,p.yg)("em",{parentName:"p"},"on the fly")," using the pipe ",(0,p.yg)("inlineCode",{parentName:"p"},"|")," operator in the PHPDoc:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Company|Contact <== can return a company OR a contact.\n */\n#[Query]\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact <== can return a company OR a contact.\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")))),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,"PHP has no native support for enum types. Hopefully, there are a number of PHP libraries that emulate enums in PHP.\nThe most commonly used library is ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum")," and GraphQLite comes with\nnative support for it."),(0,p.yg)("p",null,"You will first need to install ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[]\n */\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')))),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n/**\n * @EnumType(name="UserStatus")\n */\nclass StatusEnum extends Enum\n{\n // ...\n}\n')))),(0,p.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,p.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,p.yg)("div",{class:"alert alert--info"},'There are many enumeration library in PHP and you might be using another library. If you want to add support for your own library, this is not extremely difficult to do. You need to register a custom "RootTypeMapper" with GraphQLite. You can learn more about ',(0,p.yg)("em",null,"type mappers")," in the ",(0,p.yg)("a",{href:"internals"},'"internals" documentation'),"and ",(0,p.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/MyCLabsEnumTypeMapper.php"},"copy/paste and adapt the root type mapper used for myclabs/php-enum"),"."),(0,p.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,p.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n * @deprecated use field `name` instead\n */\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,p.yg)("p",null,"This will add the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,p.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,p.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,p.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,p.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,p.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4f59166d.37dd0e41.js b/assets/js/4f59166d.4faf7932.js similarity index 99% rename from assets/js/4f59166d.37dd0e41.js rename to assets/js/4f59166d.4faf7932.js index d7b08d97e0..fee91bb720 100644 --- a/assets/js/4f59166d.37dd0e41.js +++ b/assets/js/4f59166d.4faf7932.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1340],{13107:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>g,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>p,toc:()=>y});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,p={unversionedId:"annotations-reference",id:"version-6.1/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-6.1/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/6.1/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/annotations-reference.md",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/6.1/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/6.1/semver"}},g={},y=[{value:"@Query",id:"query",level:2},{value:"@Mutation",id:"mutation",level:2},{value:"@Type",id:"type",level:2},{value:"@ExtendType",id:"extendtype",level:2},{value:"@Input",id:"input",level:2},{value:"@Field",id:"field",level:2},{value:"@SourceField",id:"sourcefield",level:2},{value:"@MagicField",id:"magicfield",level:2},{value:"@Logged",id:"logged",level:2},{value:"@Right",id:"right",level:2},{value:"@FailWith",id:"failwith",level:2},{value:"@HideIfUnauthorized",id:"hideifunauthorized",level:2},{value:"@InjectUser",id:"injectuser",level:2},{value:"@Security",id:"security",level:2},{value:"@Factory",id:"factory",level:2},{value:"@UseInputType",id:"useinputtype",level:2},{value:"@Decorate",id:"decorate",level:2},{value:"@Autowire",id:"autowire",level:2},{value:"@HideParameter",id:"hideparameter",level:2},{value:"@Validate",id:"validate",level:2},{value:"@Assertion",id:"assertion",level:2},{value:"@EnumType",id:"enumtype",level:2}],o={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},o,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query"},"@Query"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation"},"@Mutation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type"},"@Type"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type. This is used with standard output\ntypes, as well as enum types. For input types, use the ",(0,l.yg)("a",{parentName:"p",href:"#input-annotation"},"@Input annotation")," directly on the input type or a ",(0,l.yg)("a",{parentName:"p",href:"#factory-annotation"},"@Factory annoation")," to make/return an input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class/enum for the actual type. If no "class" attribute is passed, the type applies to the current class/enum. The current class/enum is assumed to be an entity (not service). If the "class" attribute ',(0,l.yg)("em",{parentName:"td"},"is passed"),", ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/external-type-declaration"},"the class/enum annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," becomes a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype"},"@ExtendType"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input"},"@Input"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Name of the input type represented in your GraphQL schema. Defaults to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," ",(0,l.yg)("em",{parentName:"td"},"only if")," the name is not specified. If ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is specified, this will default to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", so must also be included for ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," when ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation. This primarily applies to nullable fields.")))),(0,l.yg)("h2",{id:"field"},"@Field"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield"},"@SourceField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield"},"@MagicField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If not set, no description will be shown.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged"},"@Logged"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right"},"@Right"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith"},"@FailWith"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized"},"@HideIfUnauthorized"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser"},"@InjectUser"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security"},"@Security"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory"},"@Factory"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype"},"@UseInputType"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate"},"@Decorate"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire"},"@Autowire"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter"},"@HideParameter"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate"},"@Validate"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion"},"@Assertion"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype"},(0,l.yg)("del",{parentName:"h2"},"@EnumType")),(0,l.yg)("p",null,(0,l.yg)("em",{parentName:"p"},"Deprecated: Use ",(0,l.yg)("a",{parentName:"em",href:"https://www.php.net/manual/en/language.types.enumerations.php"},"PHP 8.1's native Enums")," instead with a ",(0,l.yg)("a",{parentName:"em",href:"#type-annotation"},"@Type"),".")),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1340],{13107:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>g,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>p,toc:()=>y});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,p={unversionedId:"annotations-reference",id:"version-6.1/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-6.1/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/6.1/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/annotations-reference.md",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/6.1/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/6.1/semver"}},g={},y=[{value:"@Query",id:"query",level:2},{value:"@Mutation",id:"mutation",level:2},{value:"@Type",id:"type",level:2},{value:"@ExtendType",id:"extendtype",level:2},{value:"@Input",id:"input",level:2},{value:"@Field",id:"field",level:2},{value:"@SourceField",id:"sourcefield",level:2},{value:"@MagicField",id:"magicfield",level:2},{value:"@Logged",id:"logged",level:2},{value:"@Right",id:"right",level:2},{value:"@FailWith",id:"failwith",level:2},{value:"@HideIfUnauthorized",id:"hideifunauthorized",level:2},{value:"@InjectUser",id:"injectuser",level:2},{value:"@Security",id:"security",level:2},{value:"@Factory",id:"factory",level:2},{value:"@UseInputType",id:"useinputtype",level:2},{value:"@Decorate",id:"decorate",level:2},{value:"@Autowire",id:"autowire",level:2},{value:"@HideParameter",id:"hideparameter",level:2},{value:"@Validate",id:"validate",level:2},{value:"@Assertion",id:"assertion",level:2},{value:"@EnumType",id:"enumtype",level:2}],o={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},o,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query"},"@Query"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation"},"@Mutation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type"},"@Type"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type. This is used with standard output\ntypes, as well as enum types. For input types, use the ",(0,l.yg)("a",{parentName:"p",href:"#input-annotation"},"@Input annotation")," directly on the input type or a ",(0,l.yg)("a",{parentName:"p",href:"#factory-annotation"},"@Factory annoation")," to make/return an input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class/enum for the actual type. If no "class" attribute is passed, the type applies to the current class/enum. The current class/enum is assumed to be an entity (not service). If the "class" attribute ',(0,l.yg)("em",{parentName:"td"},"is passed"),", ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/external-type-declaration"},"the class/enum annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," becomes a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype"},"@ExtendType"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input"},"@Input"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Name of the input type represented in your GraphQL schema. Defaults to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," ",(0,l.yg)("em",{parentName:"td"},"only if")," the name is not specified. If ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is specified, this will default to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", so must also be included for ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," when ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("inlineCode",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation. This primarily applies to nullable fields.")))),(0,l.yg)("h2",{id:"field"},"@Field"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield"},"@SourceField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield"},"@MagicField"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If not set, no description will be shown.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged"},"@Logged"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right"},"@Right"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith"},"@FailWith"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized"},"@HideIfUnauthorized"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser"},"@InjectUser"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security"},"@Security"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory"},"@Factory"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/6.1/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype"},"@UseInputType"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate"},"@Decorate"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire"},"@Autowire"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter"},"@HideParameter"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate"},"@Validate"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion"},"@Assertion"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/6.1/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype"},(0,l.yg)("del",{parentName:"h2"},"@EnumType")),(0,l.yg)("p",null,(0,l.yg)("em",{parentName:"p"},"Deprecated: Use ",(0,l.yg)("a",{parentName:"em",href:"https://www.php.net/manual/en/language.types.enumerations.php"},"PHP 8.1's native Enums")," instead with a ",(0,l.yg)("a",{parentName:"em",href:"#type-annotation"},"@Type"),".")),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4f6c3156.6d101bdd.js b/assets/js/4f6c3156.5ce6d6f5.js similarity index 98% rename from assets/js/4f6c3156.6d101bdd.js rename to assets/js/4f6c3156.5ce6d6f5.js index f721c6d2ad..dc2a34dbec 100644 --- a/assets/js/4f6c3156.6d101bdd.js +++ b/assets/js/4f6c3156.5ce6d6f5.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3137],{60128:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>r,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>u,toc:()=>s});var a=t(58168),i=(t(96540),t(15680));t(67443);const l={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},o=void 0,u={unversionedId:"symfony-bundle-advanced",id:"version-6.1/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.1/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/6.1/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/symfony-bundle-advanced.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"docs",previous:{title:"Class with multiple output types",permalink:"/docs/6.1/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/6.1/laravel-package-advanced"}},r={},s=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],g={toc:s},y="wrapper";function d(e){let{components:n,...t}=e;return(0,i.yg)(y,(0,a.A)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,i.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,i.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,i.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,i.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,i.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,i.yg)("p",null,"By settings ",(0,i.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,i.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,i.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,i.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,i.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,i.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,i.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,i.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,i.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,i.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,i.yg)("p",null,"The mutation below will log-in a user:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,i.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,i.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,i.yg)("p",null,"In Symfony, user objects implement ",(0,i.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,i.yg)("p",null,"If you want to get more fields, just add the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")),(0,i.yg)("p",null,"You can now query this field using an ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,i.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,i.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,i.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,i.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,i.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3137],{60128:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>r,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>u,toc:()=>s});var a=t(58168),i=(t(96540),t(15680));t(67443);const l={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},o=void 0,u={unversionedId:"symfony-bundle-advanced",id:"version-6.1/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.1/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/6.1/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/symfony-bundle-advanced.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"docs",previous:{title:"Class with multiple output types",permalink:"/docs/6.1/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/6.1/laravel-package-advanced"}},r={},s=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],g={toc:s},y="wrapper";function d(e){let{components:n,...t}=e;return(0,i.yg)(y,(0,a.A)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,i.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,i.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,i.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,i.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,i.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,i.yg)("p",null,"By settings ",(0,i.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,i.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,i.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,i.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,i.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,i.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,i.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,i.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,i.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,i.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,i.yg)("p",null,"The mutation below will log-in a user:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,i.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,i.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,i.yg)("p",null,"In Symfony, user objects implement ",(0,i.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,i.yg)("p",null,"If you want to get more fields, just add the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")),(0,i.yg)("p",null,"You can now query this field using an ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,i.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,i.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,i.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,i.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,i.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/50122f86.caa64e70.js b/assets/js/50122f86.978eb0e7.js similarity index 54% rename from assets/js/50122f86.caa64e70.js rename to assets/js/50122f86.978eb0e7.js index 166b13d9ea..69be362cd3 100644 --- a/assets/js/50122f86.caa64e70.js +++ b/assets/js/50122f86.978eb0e7.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8267],{60331:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>u,toc:()=>r});var a=t(58168),i=(t(96540),t(15680));t(67443);const l={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features",original_id:"symfony-bundle-advanced"},o=void 0,u={unversionedId:"symfony-bundle-advanced",id:"version-4.0/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony.",source:"@site/versioned_docs/version-4.0/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/4.0/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/symfony-bundle-advanced.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features",original_id:"symfony-bundle-advanced"},sidebar:"version-4.0/docs",previous:{title:"Class with multiple output types",permalink:"/docs/4.0/multiple_output_types"},next:{title:"Laravel specific features",permalink:"/docs/4.0/laravel-package-advanced"}},s={},r=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],g={toc:r},y="wrapper";function d(e){let{components:n,...t}=e;return(0,i.yg)(y,(0,a.A)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,i.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,i.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,i.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,i.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,i.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,i.yg)("p",null,"By settings ",(0,i.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,i.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,i.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,i.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,i.yg)("p",null,"The mutation below will log-in a user:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,i.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,i.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,i.yg)("p",null,"In Symfony, user objects implement ",(0,i.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,i.yg)("p",null,"If you want to get more fields, just add the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")),(0,i.yg)("p",null,"You can now query this field using an ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,i.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,i.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,i.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,i.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,i.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8267],{60331:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>u,toc:()=>r});var a=t(58168),l=(t(96540),t(15680));t(67443);const i={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features",original_id:"symfony-bundle-advanced"},o=void 0,u={unversionedId:"symfony-bundle-advanced",id:"version-4.0/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony.",source:"@site/versioned_docs/version-4.0/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/4.0/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/symfony-bundle-advanced.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features",original_id:"symfony-bundle-advanced"},sidebar:"version-4.0/docs",previous:{title:"Class with multiple output types",permalink:"/docs/4.0/multiple_output_types"},next:{title:"Laravel specific features",permalink:"/docs/4.0/laravel-package-advanced"}},s={},r=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],g={toc:r},y="wrapper";function d(e){let{components:n,...t}=e;return(0,l.yg)(y,(0,a.A)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/504e6c2d.3a3311b1.js b/assets/js/504e6c2d.5276e8fc.js similarity index 89% rename from assets/js/504e6c2d.3a3311b1.js rename to assets/js/504e6c2d.5276e8fc.js index e083d3213b..94f4b46587 100644 --- a/assets/js/504e6c2d.3a3311b1.js +++ b/assets/js/504e6c2d.5276e8fc.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6894],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),u=a(56347),i=a(57485),s=a(31682),p=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function f(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=c(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,s]=h({queryString:a,groupId:n}),[d,f]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),g=(()=>{const e=i??d;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{g&&u(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),s(e),f(e)}),[s,f,l]),tabValues:l}}var g=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:u,selectValue:i,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==u&&(d(t),i(n))},m=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:c},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":u===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},79959:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>s,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const u={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},i=void 0,s={unversionedId:"file-uploads",id:"version-6.0/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-6.0/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/6.0/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/file-uploads.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"docs",previous:{title:"Prefetching records",permalink:"/docs/6.0/prefetch-method"},next:{title:"Pagination",permalink:"/docs/6.0/pagination"}},p={},d=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],c={toc:d},m="wrapper";function h(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,r.yg)("p",null,"You must start by installing this package:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,r.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,r.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,r.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,r.yg)("p",null,"In order to use this, you must first be sure that the ",(0,r.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,r.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,r.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,r.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")))),(0,r.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,r.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6894],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),u=a(56347),i=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function f(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,s]=h({queryString:a,groupId:n}),[c,f]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),g=(()=>{const e=i??c;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{g&&u(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),s(e),f(e)}),[s,f,l]),tabValues:l}}var g=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:u,selectValue:i,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==u&&(c(t),i(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":u===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},79959:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const u={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},i=void 0,s={unversionedId:"file-uploads",id:"version-6.0/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-6.0/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/6.0/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/file-uploads.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"docs",previous:{title:"Prefetching records",permalink:"/docs/6.0/prefetch-method"},next:{title:"Pagination",permalink:"/docs/6.0/pagination"}},p={},c=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],d={toc:c},m="wrapper";function h(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,r.yg)("p",null,"You must start by installing this package:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,r.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,r.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,r.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,r.yg)("p",null,"In order to use this, you must first be sure that the ",(0,r.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,r.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,r.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,r.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")))),(0,r.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,r.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/509d2004.72acb360.js b/assets/js/509d2004.515098d6.js similarity index 96% rename from assets/js/509d2004.72acb360.js rename to assets/js/509d2004.515098d6.js index 91b6309f59..b3a771bde0 100644 --- a/assets/js/509d2004.72acb360.js +++ b/assets/js/509d2004.515098d6.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8457],{28570:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>g,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var t=n(58168),r=(n(96540),n(15680));n(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-5.0/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-5.0/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/5.0/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/argument-resolving.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"version-5.0/docs",previous:{title:"Custom annotations",permalink:"/docs/5.0/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/5.0/extend-input-type"}},s={},p=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},d="wrapper";function g(e){let{components:a,...n}=e;return(0,r.yg)(d,(0,t.A)({},m,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Autowire")," annotation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8457],{28570:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var t=n(58168),r=(n(96540),n(15680));n(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-5.0/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-5.0/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/5.0/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/argument-resolving.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"version-5.0/docs",previous:{title:"Custom annotations",permalink:"/docs/5.0/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/5.0/extend-input-type"}},s={},p=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},g="wrapper";function d(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},m,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Autowire")," annotation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5285d58e.65ac0f19.js b/assets/js/5285d58e.dc0572a0.js similarity index 99% rename from assets/js/5285d58e.65ac0f19.js rename to assets/js/5285d58e.dc0572a0.js index 0ba88d2ab6..419e276f08 100644 --- a/assets/js/5285d58e.65ac0f19.js +++ b/assets/js/5285d58e.dc0572a0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5675],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),l=n(20053);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>$});var a=n(58168),l=n(96540),r=n(20053),i=n(23104),o=n(56347),u=n(57485),p=n(31682),s=n(89466);function c(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:l}}=e;return{value:t,label:n,attributes:a,default:l}}))}function d(e){const{values:t,children:n}=e;return(0,l.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=d(e),[i,o]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[u,p]=g({queryString:n,groupId:a}),[c,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,s.Dv)(n);return[a,(0,l.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),h=(()=>{const e=u??c;return y({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:i,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),p(e),m(e)}),[p,m,r]),tabValues:r}}var h=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:u,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=s.indexOf(t),a=p[n].value;a!==o&&(c(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=s.indexOf(e.currentTarget)+1;t=s[n]??s[0];break}case"ArrowLeft":{const n=s.indexOf(e.currentTarget)-1;t=s[n]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:i}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:d},i,{className:(0,r.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function I(e){const t=m(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,t)),l.createElement(v,(0,a.A)({},e,t)))}function $(e){const t=(0,h.A)();return l.createElement(I,(0,a.A)({key:String(t)},e))}},25529:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>u,default:()=>g,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var a=n(58168),l=(n(96540),n(15680)),r=(n(67443),n(11470)),i=n(19365);const o={id:"input-types",title:"Input types",sidebar_label:"Input types"},u=void 0,p={unversionedId:"input-types",id:"version-7.0.0/input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-7.0.0/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/input-types.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"docs",previous:{title:"External type declaration",permalink:"/docs/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/inheritance-interfaces"}},s={},c=[{value:"#[Input] Attribute",id:"input-attribute",level:2},{value:"Multiple Input Types from the same class",id:"multiple-input-types-from-the-same-class",level:3},{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3}],d={toc:c},y="wrapper";function g(e){let{components:t,...n}=e;return(0,l.yg)(y,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,l.yg)("p",null,"Your GraphQL query might look like this:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,l.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,l.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,l.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,l.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using the ",(0,l.yg)("a",{parentName:"p",href:"input-attribute"},(0,l.yg)("inlineCode",{parentName:"a"},"#[Input]")," attribute")," or a ",(0,l.yg)("a",{parentName:"p",href:"factory"},"Factory method"),"."),(0,l.yg)("h2",{id:"input-attribute"},"#","[","Input","]"," Attribute"),(0,l.yg)("p",null,"Using the ",(0,l.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute, we can transform the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," class, in the example above, into an input type. Just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute to the corresponding properties:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private ?string $name = null;\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function setName(string $name): void\n {\n $this->name = $name;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Input\n */\nclass Location\n{\n\n /**\n * @Field\n * @var string|null\n */\n private ?string $name = null;\n\n /**\n * @Field\n * @var float\n */\n private $latitude;\n\n /**\n * @Field\n * @var float\n */\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function setName(string $name): void\n {\n $this->name = $name;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"Now if you call the ",(0,l.yg)("inlineCode",{parentName:"p"},"getCities")," query, from the controller in the first example, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with the user provided, ",(0,l.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,l.yg)("inlineCode",{parentName:"p"},"longitude")," properties, and passed to the controller as a parameter."),(0,l.yg)("p",null,"There are some important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"The ",(0,l.yg)("inlineCode",{parentName:"li"},"@Field")," annotation is recognized on properties for Input Type, as well as setters."),(0,l.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,l.yg)("ul",{parentName:"li"},(0,l.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,l.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort - no constructor required."),(0,l.yg)("li",{parentName:"ul"},"For private or protected properties implemented, a public setter is required (if they are not set via the constructor). For example ",(0,l.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),". You can also put the ",(0,l.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on the setter, instead of the property, allowing you to have use many other attributes (",(0,l.yg)("inlineCode",{parentName:"li"},"Security"),", ",(0,l.yg)("inlineCode",{parentName:"li"},"Right"),", ",(0,l.yg)("inlineCode",{parentName:"li"},"Autowire"),", etc.)."))),(0,l.yg)("li",{parentName:"ul"},"For validation of these Input Types, see the ",(0,l.yg)("a",{parentName:"li",href:"validation#custom-inputtype-validation"},"Custom InputType Validation section"),"."),(0,l.yg)("li",{parentName:"ul"},"It's advised to use the ",(0,l.yg)("inlineCode",{parentName:"li"},"#[Input]")," attribute on DTO style input type objects and not directly on your model objects. Using it on your model objects can cause coupling in undesirable ways.")),(0,l.yg)("h3",{id:"multiple-input-types-from-the-same-class"},"Multiple Input Types from the same class"),(0,l.yg)("p",null,"Simple usage of the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),' annotation on a class creates a GraphQL input named by class name + "Input" suffix if a class name does not end with it already. Ex. ',(0,l.yg)("inlineCode",{parentName:"p"},"LocationInput")," for ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," class."),(0,l.yg)("p",null,"You can add multiple ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotations to the same class, give them different names and link different fields.\nConsider the following example:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n protected ?int $age;\n\n\n #[Field]\n public function setAge(?int $age): void\n {\n $this->age = $age;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Input(name="CreateUserInput", default=true)\n * @Input(name="UpdateUserInput", update=true)\n */\nclass UserInput\n{\n\n /**\n * @Field()\n * @var string\n */\n public $username;\n\n /**\n * @Field(for="CreateUserInput")\n * @var string\n */\n public string $email;\n\n /**\n * @Field(for="CreateUserInput", inputType="String!")\n * @Field(for="UpdateUserInput", inputType="String")\n * @var string|null\n */\n public $password;\n\n /** @var int|null */\n protected $age;\n\n /**\n * @Field()\n * @param int|null $age\n */\n public function setAge(?int $age): void\n {\n $this->age = $age;\n }\n}\n')))),(0,l.yg)("p",null,"There are 2 input types added to the ",(0,l.yg)("inlineCode",{parentName:"p"},"UserInput")," class: ",(0,l.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,l.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,l.yg)("p",null,"Note that ",(0,l.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,l.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 7 they will be set to ",(0,l.yg)("inlineCode",{parentName:"p"},"null"),", while in PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."),(0,l.yg)("h2",{id:"factory"},"Factory"),(0,l.yg)("p",null,"A ",(0,l.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,l.yg)("p",null,"Here is an example of factory:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")))),(0,l.yg)("p",null,"and now, you can run query like this:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,l.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,l.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,l.yg)("p",null,"A few important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,l.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,l.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,l.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,l.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")))),(0,l.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')))),(0,l.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,l.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,l.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,l.yg)("p",null,"You can use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,l.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')))),(0,l.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,l.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,l.yg)("p",null,"Here is an annotated sample:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')))),(0,l.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,l.yg)("p",null,"Image your ",(0,l.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,l.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')))),(0,l.yg)("p",null,"With the ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,l.yg)("p",null,"To be able to hide an argument, the argument must have a default value."))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5675],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),l=n(20053);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>$});var a=n(58168),l=n(96540),r=n(20053),i=n(23104),o=n(56347),u=n(57485),p=n(31682),s=n(89466);function c(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:l}}=e;return{value:t,label:n,attributes:a,default:l}}))}function d(e){const{values:t,children:n}=e;return(0,l.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=d(e),[i,o]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[u,p]=g({queryString:n,groupId:a}),[c,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,s.Dv)(n);return[a,(0,l.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),h=(()=>{const e=u??c;return y({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:i,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),p(e),m(e)}),[p,m,r]),tabValues:r}}var h=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:u,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=s.indexOf(t),a=p[n].value;a!==o&&(c(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=s.indexOf(e.currentTarget)+1;t=s[n]??s[0];break}case"ArrowLeft":{const n=s.indexOf(e.currentTarget)-1;t=s[n]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:i}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:d},i,{className:(0,r.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function I(e){const t=m(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,t)),l.createElement(v,(0,a.A)({},e,t)))}function $(e){const t=(0,h.A)();return l.createElement(I,(0,a.A)({key:String(t)},e))}},25529:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>u,default:()=>g,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var a=n(58168),l=(n(96540),n(15680)),r=(n(67443),n(11470)),i=n(19365);const o={id:"input-types",title:"Input types",sidebar_label:"Input types"},u=void 0,p={unversionedId:"input-types",id:"version-7.0.0/input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-7.0.0/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/input-types.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"docs",previous:{title:"External type declaration",permalink:"/docs/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/inheritance-interfaces"}},s={},c=[{value:"#[Input] Attribute",id:"input-attribute",level:2},{value:"Multiple Input Types from the same class",id:"multiple-input-types-from-the-same-class",level:3},{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3}],d={toc:c},y="wrapper";function g(e){let{components:t,...n}=e;return(0,l.yg)(y,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,l.yg)("p",null,"Your GraphQL query might look like this:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,l.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,l.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,l.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,l.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using the ",(0,l.yg)("a",{parentName:"p",href:"input-attribute"},(0,l.yg)("inlineCode",{parentName:"a"},"#[Input]")," attribute")," or a ",(0,l.yg)("a",{parentName:"p",href:"factory"},"Factory method"),"."),(0,l.yg)("h2",{id:"input-attribute"},"#","[","Input","]"," Attribute"),(0,l.yg)("p",null,"Using the ",(0,l.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute, we can transform the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," class, in the example above, into an input type. Just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute to the corresponding properties:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private ?string $name = null;\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function setName(string $name): void\n {\n $this->name = $name;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Input\n */\nclass Location\n{\n\n /**\n * @Field\n * @var string|null\n */\n private ?string $name = null;\n\n /**\n * @Field\n * @var float\n */\n private $latitude;\n\n /**\n * @Field\n * @var float\n */\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function setName(string $name): void\n {\n $this->name = $name;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"Now if you call the ",(0,l.yg)("inlineCode",{parentName:"p"},"getCities")," query, from the controller in the first example, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with the user provided, ",(0,l.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,l.yg)("inlineCode",{parentName:"p"},"longitude")," properties, and passed to the controller as a parameter."),(0,l.yg)("p",null,"There are some important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"The ",(0,l.yg)("inlineCode",{parentName:"li"},"@Field")," annotation is recognized on properties for Input Type, as well as setters."),(0,l.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,l.yg)("ul",{parentName:"li"},(0,l.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,l.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort - no constructor required."),(0,l.yg)("li",{parentName:"ul"},"For private or protected properties implemented, a public setter is required (if they are not set via the constructor). For example ",(0,l.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),". You can also put the ",(0,l.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on the setter, instead of the property, allowing you to have use many other attributes (",(0,l.yg)("inlineCode",{parentName:"li"},"Security"),", ",(0,l.yg)("inlineCode",{parentName:"li"},"Right"),", ",(0,l.yg)("inlineCode",{parentName:"li"},"Autowire"),", etc.)."))),(0,l.yg)("li",{parentName:"ul"},"For validation of these Input Types, see the ",(0,l.yg)("a",{parentName:"li",href:"validation#custom-inputtype-validation"},"Custom InputType Validation section"),"."),(0,l.yg)("li",{parentName:"ul"},"It's advised to use the ",(0,l.yg)("inlineCode",{parentName:"li"},"#[Input]")," attribute on DTO style input type objects and not directly on your model objects. Using it on your model objects can cause coupling in undesirable ways.")),(0,l.yg)("h3",{id:"multiple-input-types-from-the-same-class"},"Multiple Input Types from the same class"),(0,l.yg)("p",null,"Simple usage of the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),' annotation on a class creates a GraphQL input named by class name + "Input" suffix if a class name does not end with it already. Ex. ',(0,l.yg)("inlineCode",{parentName:"p"},"LocationInput")," for ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," class."),(0,l.yg)("p",null,"You can add multiple ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotations to the same class, give them different names and link different fields.\nConsider the following example:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n protected ?int $age;\n\n\n #[Field]\n public function setAge(?int $age): void\n {\n $this->age = $age;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Input(name="CreateUserInput", default=true)\n * @Input(name="UpdateUserInput", update=true)\n */\nclass UserInput\n{\n\n /**\n * @Field()\n * @var string\n */\n public $username;\n\n /**\n * @Field(for="CreateUserInput")\n * @var string\n */\n public string $email;\n\n /**\n * @Field(for="CreateUserInput", inputType="String!")\n * @Field(for="UpdateUserInput", inputType="String")\n * @var string|null\n */\n public $password;\n\n /** @var int|null */\n protected $age;\n\n /**\n * @Field()\n * @param int|null $age\n */\n public function setAge(?int $age): void\n {\n $this->age = $age;\n }\n}\n')))),(0,l.yg)("p",null,"There are 2 input types added to the ",(0,l.yg)("inlineCode",{parentName:"p"},"UserInput")," class: ",(0,l.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,l.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,l.yg)("p",null,"Note that ",(0,l.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,l.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 7 they will be set to ",(0,l.yg)("inlineCode",{parentName:"p"},"null"),", while in PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."),(0,l.yg)("h2",{id:"factory"},"Factory"),(0,l.yg)("p",null,"A ",(0,l.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,l.yg)("p",null,"Here is an example of factory:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")))),(0,l.yg)("p",null,"and now, you can run query like this:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,l.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,l.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,l.yg)("p",null,"A few important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,l.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,l.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,l.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,l.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")))),(0,l.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')))),(0,l.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,l.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,l.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,l.yg)("p",null,"You can use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,l.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')))),(0,l.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,l.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,l.yg)("p",null,"Here is an annotated sample:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')))),(0,l.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,l.yg)("p",null,"Image your ",(0,l.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,l.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')))),(0,l.yg)("p",null,"With the ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,l.yg)("p",null,"To be able to hide an argument, the argument must have a default value."))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/528fe65e.933eb23e.js b/assets/js/528fe65e.854670a4.js similarity index 99% rename from assets/js/528fe65e.933eb23e.js rename to assets/js/528fe65e.854670a4.js index 4de35108c6..c10648fc2f 100644 --- a/assets/js/528fe65e.933eb23e.js +++ b/assets/js/528fe65e.854670a4.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1027],{65009:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>l,contentTitle:()=>s,default:()=>c,frontMatter:()=>i,metadata:()=>r,toc:()=>o});var t=a(58168),p=(a(96540),a(15680));a(67443);const i={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},s=void 0,r={unversionedId:"type-mapping",id:"type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/docs/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/next/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/type-mapping.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"docs",previous:{title:"Subscriptions",permalink:"/docs/next/subscriptions"},next:{title:"Autowiring services",permalink:"/docs/next/autowiring"}},l={},o=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Enum types with myclabs/php-enum",id:"enum-types-with-myclabsphp-enum",level:3},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],u={toc:o},y="wrapper";function c(e){let{components:n,...a}=e;return(0,p.yg)(y,(0,t.A)({},u,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/next/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"#[Type]")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"#[Field]")," attributes:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n')),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"#[Type]")," attribute on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n')),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/next/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n")),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"Union types for return are supported in GraphQLite as of version 6.0:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\npublic function companyOrContact(int $id): Company|Contact\n{\n // Some code that returns a company or a contact.\n}\n")),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("p",null,"PHP 8.1 introduced native support for Enums. GraphQLite now also supports native enums as of version 5.1."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nenum Status: string\n{\n case ON = 'on';\n case OFF = 'off';\n case PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return User[]\n */\n#[Query]\npublic function users(Status $status): array\n{\n if ($status === Status::ON) {\n // Your logic\n }\n // ...\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: Status!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"name")," property on the ",(0,p.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'namespace Model\\User;\n\n#[Type(name: "UserStatus")]\nenum Status: string\n{\n // ...\n}\n')),(0,p.yg)("h3",{id:"enum-types-with-myclabsphp-enum"},"Enum types with myclabs/php-enum"),(0,p.yg)("div",{class:"alert alert--danger"},"This implementation is now deprecated and will be removed in the future. You are advised to use native enums instead."),(0,p.yg)("p",null,(0,p.yg)("em",{parentName:"p"},"Prior to version 5.1, GraphQLite only supported Enums through the 3rd party library, ",(0,p.yg)("a",{parentName:"em",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),". If you'd like to use this implementation you'll first need to add this library as a dependency to your application.")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"#[EnumType]")," attribute:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n')),(0,p.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,p.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,p.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,p.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation. Note that a description (reason) is required for the annotation to be rendered."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @deprecated use field `name` instead\n */\n #[Field]\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,p.yg)("p",null,"This will add the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,p.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,p.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,p.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,p.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,p.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1027],{65009:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>l,contentTitle:()=>s,default:()=>c,frontMatter:()=>i,metadata:()=>r,toc:()=>o});var t=a(58168),p=(a(96540),a(15680));a(67443);const i={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},s=void 0,r={unversionedId:"type-mapping",id:"type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/docs/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/next/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/type-mapping.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"docs",previous:{title:"Subscriptions",permalink:"/docs/next/subscriptions"},next:{title:"Autowiring services",permalink:"/docs/next/autowiring"}},l={},o=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Enum types with myclabs/php-enum",id:"enum-types-with-myclabsphp-enum",level:3},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],u={toc:o},y="wrapper";function c(e){let{components:n,...a}=e;return(0,p.yg)(y,(0,t.A)({},u,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/next/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"#[Type]")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"#[Field]")," attributes:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n')),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"#[Type]")," attribute on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n')),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/next/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n")),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"Union types for return are supported in GraphQLite as of version 6.0:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\npublic function companyOrContact(int $id): Company|Contact\n{\n // Some code that returns a company or a contact.\n}\n")),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("p",null,"PHP 8.1 introduced native support for Enums. GraphQLite now also supports native enums as of version 5.1."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nenum Status: string\n{\n case ON = 'on';\n case OFF = 'off';\n case PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return User[]\n */\n#[Query]\npublic function users(Status $status): array\n{\n if ($status === Status::ON) {\n // Your logic\n }\n // ...\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: Status!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"name")," property on the ",(0,p.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'namespace Model\\User;\n\n#[Type(name: "UserStatus")]\nenum Status: string\n{\n // ...\n}\n')),(0,p.yg)("h3",{id:"enum-types-with-myclabsphp-enum"},"Enum types with myclabs/php-enum"),(0,p.yg)("div",{class:"alert alert--danger"},"This implementation is now deprecated and will be removed in the future. You are advised to use native enums instead."),(0,p.yg)("p",null,(0,p.yg)("em",{parentName:"p"},"Prior to version 5.1, GraphQLite only supported Enums through the 3rd party library, ",(0,p.yg)("a",{parentName:"em",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),". If you'd like to use this implementation you'll first need to add this library as a dependency to your application.")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"#[EnumType]")," attribute:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n')),(0,p.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,p.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,p.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,p.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation. Note that a description (reason) is required for the annotation to be rendered."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @deprecated use field `name` instead\n */\n #[Field]\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,p.yg)("p",null,"This will add the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,p.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,p.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,p.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,p.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,p.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/54c144e4.7075bb1b.js b/assets/js/54c144e4.849468f9.js similarity index 96% rename from assets/js/54c144e4.7075bb1b.js rename to assets/js/54c144e4.849468f9.js index 75d085ae9f..22163e1024 100644 --- a/assets/js/54c144e4.7075bb1b.js +++ b/assets/js/54c144e4.849468f9.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7735],{46368:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>r,metadata:()=>l,toc:()=>o});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"},s=void 0,l={unversionedId:"inheritance",id:"version-4.0/inheritance",title:"Inheritance and interfaces",description:"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces.",source:"@site/versioned_docs/version-4.0/inheritance.md",sourceDirName:".",slug:"/inheritance",permalink:"/docs/4.0/inheritance",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/inheritance.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"}},c={},o=[],p={toc:o},d="wrapper";function h(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("div",{class:"alert alert--warning"},"Right now, there is no way to explicitly declare a GraphQL interface using GraphQLite.",(0,i.yg)("br",null),"GraphQLite automatically declares interfaces when it sees an inheritance relationship between to classes that are GraphQL types."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7735],{46368:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>r,metadata:()=>l,toc:()=>o});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"},s=void 0,l={unversionedId:"inheritance",id:"version-4.0/inheritance",title:"Inheritance and interfaces",description:"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces.",source:"@site/versioned_docs/version-4.0/inheritance.md",sourceDirName:".",slug:"/inheritance",permalink:"/docs/4.0/inheritance",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/inheritance.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"inheritance",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance"}},c={},o=[],p={toc:o},d="wrapper";function h(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("div",{class:"alert alert--warning"},"Right now, there is no way to explicitly declare a GraphQL interface using GraphQLite.",(0,i.yg)("br",null),"GraphQLite automatically declares interfaces when it sees an inheritance relationship between to classes that are GraphQL types."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/54ca8693.51041cd0.js b/assets/js/54ca8693.48b03a2b.js similarity index 97% rename from assets/js/54ca8693.51041cd0.js rename to assets/js/54ca8693.48b03a2b.js index 7ac9e3e652..a0ba064236 100644 --- a/assets/js/54ca8693.51041cd0.js +++ b/assets/js/54ca8693.48b03a2b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6766],{7205:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var a=t(58168),r=(t(96540),t(15680));t(67443);const l={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},i=void 0,o={unversionedId:"symfony-bundle",id:"version-3.0/symfony-bundle",title:"Getting started with Symfony",description:"The GraphQLite bundle is compatible with Symfony 4.x.",source:"@site/versioned_docs/version-3.0/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/3.0/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/symfony-bundle.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},sidebar:"version-3.0/docs",previous:{title:"Getting Started",permalink:"/docs/3.0/getting-started"},next:{title:"Laravel package",permalink:"/docs/3.0/laravel-package"}},s={},p=[{value:"Installation",id:"installation",level:2}],d={toc:p},y="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Symfony 4.x"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"The Symfony Flex recipe is not yet available."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,r.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,r.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"app/AppKernel.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var a=t(58168),r=(t(96540),t(15680));t(67443);const l={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},i=void 0,o={unversionedId:"symfony-bundle",id:"version-3.0/symfony-bundle",title:"Getting started with Symfony",description:"The GraphQLite bundle is compatible with Symfony 4.x.",source:"@site/versioned_docs/version-3.0/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/3.0/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/symfony-bundle.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},sidebar:"version-3.0/docs",previous:{title:"Getting Started",permalink:"/docs/3.0/getting-started"},next:{title:"Laravel package",permalink:"/docs/3.0/laravel-package"}},s={},p=[{value:"Installation",id:"installation",level:2}],d={toc:p},y="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Symfony 4.x"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"The Symfony Flex recipe is not yet available."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,r.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,r.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"app/AppKernel.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-4.2/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/versioned_docs/version-4.2/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/4.2/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/semver.md",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"version-4.2/docs",previous:{title:"Annotations reference",permalink:"/docs/4.2/annotations-reference"},next:{title:"Changelog",permalink:"/docs/4.2/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1930],{68168:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-4.2/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/versioned_docs/version-4.2/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/4.2/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/semver.md",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"version-4.2/docs",previous:{title:"Annotations reference",permalink:"/docs/4.2/annotations-reference"},next:{title:"Changelog",permalink:"/docs/4.2/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/56279b5e.dd772e67.js b/assets/js/56279b5e.78110a26.js similarity index 99% rename from assets/js/56279b5e.dd772e67.js rename to assets/js/56279b5e.78110a26.js index 018eee68c0..e239a7b760 100644 --- a/assets/js/56279b5e.dd772e67.js +++ b/assets/js/56279b5e.78110a26.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4032],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),l=n(20053);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),l=n(96540),r=n(20053),i=n(23104),o=n(56347),u=n(57485),p=n(31682),s=n(89466);function c(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:l}}=e;return{value:t,label:n,attributes:a,default:l}}))}function d(e){const{values:t,children:n}=e;return(0,l.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=d(e),[i,o]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[u,p]=g({queryString:n,groupId:a}),[c,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,s.Dv)(n);return[a,(0,l.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),h=(()=>{const e=u??c;return y({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:i,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),p(e),m(e)}),[p,m,r]),tabValues:r}}var h=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:u,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=s.indexOf(t),a=p[n].value;a!==o&&(c(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=s.indexOf(e.currentTarget)+1;t=s[n]??s[0];break}case"ArrowLeft":{const n=s.indexOf(e.currentTarget)-1;t=s[n]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:i}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:d},i,{className:(0,r.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function I(e){const t=m(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,t)),l.createElement(v,(0,a.A)({},e,t)))}function T(e){const t=(0,h.A)();return l.createElement(I,(0,a.A)({key:String(t)},e))}},32902:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>u,default:()=>g,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var a=n(58168),l=(n(96540),n(15680)),r=(n(67443),n(11470)),i=n(19365);const o={id:"input-types",title:"Input types",sidebar_label:"Input types"},u=void 0,p={unversionedId:"input-types",id:"version-4.2/input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-4.2/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/4.2/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/input-types.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"version-4.2/docs",previous:{title:"External type declaration",permalink:"/docs/4.2/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/4.2/inheritance-interfaces"}},s={},c=[{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3},{value:"@Input Annotation",id:"input-annotation",level:2},{value:"Multiple input types per one class",id:"multiple-input-types-per-one-class",level:3}],d={toc:c},y="wrapper";function g(e){let{components:t,...n}=e;return(0,l.yg)(y,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,l.yg)("p",null,"Your GraphQL query might look like this:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,l.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,l.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,l.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,l.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using ",(0,l.yg)("strong",{parentName:"p"},"Factory")," or annotating the class with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),"."),(0,l.yg)("h2",{id:"factory"},"Factory"),(0,l.yg)("p",null,"A ",(0,l.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,l.yg)("p",null,"Here is an example of factory:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")))),(0,l.yg)("p",null,"and now, you can run query like this:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,l.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,l.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,l.yg)("p",null,"A few important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,l.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,l.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,l.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,l.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")))),(0,l.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')))),(0,l.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,l.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,l.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,l.yg)("p",null,"You can use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,l.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')))),(0,l.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,l.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,l.yg)("p",null,"Here is an annotated sample:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')))),(0,l.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,l.yg)("p",null,"Image your ",(0,l.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,l.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')))),(0,l.yg)("p",null,"With the ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,l.yg)("p",null,"To be able to hide an argument, the argument must have a default value."),(0,l.yg)("h2",{id:"input-annotation"},"@Input Annotation"),(0,l.yg)("p",null,"Let's transform ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," class into an input type by adding ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation to it and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to corresponding properties:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Input\n */\nclass Location\n{\n\n /**\n * @Field\n * @var float\n */\n private $latitude;\n\n /**\n * @Field\n * @var float\n */\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"Now if you call ",(0,l.yg)("inlineCode",{parentName:"p"},"getCities()")," query you can pass the location input in the same way as with factories.\nThe ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with provided ",(0,l.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,l.yg)("inlineCode",{parentName:"p"},"longitude")," and passed to the controller as a parameter."),(0,l.yg)("p",null,"There are some important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"@Field")," annotation is recognized only on properties for Input Type."),(0,l.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,l.yg)("ul",{parentName:"li"},(0,l.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,l.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort."),(0,l.yg)("li",{parentName:"ul"},"For private or protected properties implemented public setter is required (if they are not set via constructor). For example ",(0,l.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),".")))),(0,l.yg)("h3",{id:"multiple-input-types-per-one-class"},"Multiple input types per one class"),(0,l.yg)("p",null,"Simple usage of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),' annotation on a class creates an GraphQl input named by class name + "Input" suffix if a class name does not end with it already.\nYou can add multiple ',(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotations to the same class, give them different names and link different fields.\nConsider the following example:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n #[Field]\n public ?int $age;\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Input(name="CreateUserInput", default=true)\n * @Input(name="UpdateUserInput", update=true)\n */\nclass UserInput\n{\n\n /**\n * @Field()\n * @var string\n */\n public $username;\n\n /**\n * @Field(for="CreateUserInput")\n * @var string\n */\n public string $email;\n\n /**\n * @Field(for="CreateUserInput", inputType="String!")\n * @Field(for="UpdateUserInput", inputType="String")\n * @var string|null\n */\n public $password;\n\n /**\n * @Field()\n * @var int|null\n */\n public $age;\n}\n')))),(0,l.yg)("p",null,"There are 2 input types created for just one class: ",(0,l.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,l.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,l.yg)("p",null,"Note that ",(0,l.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,l.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 7 they will be set to ",(0,l.yg)("inlineCode",{parentName:"p"},"null"),", while in PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4032],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),l=n(20053);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),l=n(96540),r=n(20053),i=n(23104),o=n(56347),u=n(57485),p=n(31682),s=n(89466);function c(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:l}}=e;return{value:t,label:n,attributes:a,default:l}}))}function d(e){const{values:t,children:n}=e;return(0,l.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=d(e),[i,o]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[u,p]=g({queryString:n,groupId:a}),[c,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,s.Dv)(n);return[a,(0,l.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),h=(()=>{const e=u??c;return y({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:i,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),p(e),m(e)}),[p,m,r]),tabValues:r}}var h=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:u,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=s.indexOf(t),a=p[n].value;a!==o&&(c(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=s.indexOf(e.currentTarget)+1;t=s[n]??s[0];break}case"ArrowLeft":{const n=s.indexOf(e.currentTarget)-1;t=s[n]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:i}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:d},i,{className:(0,r.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function I(e){const t=m(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,t)),l.createElement(v,(0,a.A)({},e,t)))}function T(e){const t=(0,h.A)();return l.createElement(I,(0,a.A)({key:String(t)},e))}},32902:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>u,default:()=>g,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var a=n(58168),l=(n(96540),n(15680)),r=(n(67443),n(11470)),i=n(19365);const o={id:"input-types",title:"Input types",sidebar_label:"Input types"},u=void 0,p={unversionedId:"input-types",id:"version-4.2/input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-4.2/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/4.2/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/input-types.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"version-4.2/docs",previous:{title:"External type declaration",permalink:"/docs/4.2/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/4.2/inheritance-interfaces"}},s={},c=[{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3},{value:"@Input Annotation",id:"input-annotation",level:2},{value:"Multiple input types per one class",id:"multiple-input-types-per-one-class",level:3}],d={toc:c},y="wrapper";function g(e){let{components:t,...n}=e;return(0,l.yg)(y,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,l.yg)("p",null,"Your GraphQL query might look like this:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,l.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,l.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,l.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,l.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using ",(0,l.yg)("strong",{parentName:"p"},"Factory")," or annotating the class with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),"."),(0,l.yg)("h2",{id:"factory"},"Factory"),(0,l.yg)("p",null,"A ",(0,l.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,l.yg)("p",null,"Here is an example of factory:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")))),(0,l.yg)("p",null,"and now, you can run query like this:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,l.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,l.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,l.yg)("p",null,"A few important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,l.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,l.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,l.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,l.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")))),(0,l.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')))),(0,l.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,l.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,l.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,l.yg)("p",null,"You can use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,l.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')))),(0,l.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,l.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,l.yg)("p",null,"Here is an annotated sample:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')))),(0,l.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,l.yg)("p",null,"Image your ",(0,l.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,l.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')))),(0,l.yg)("p",null,"With the ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,l.yg)("p",null,"To be able to hide an argument, the argument must have a default value."),(0,l.yg)("h2",{id:"input-annotation"},"@Input Annotation"),(0,l.yg)("p",null,"Let's transform ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," class into an input type by adding ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation to it and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to corresponding properties:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Input\n */\nclass Location\n{\n\n /**\n * @Field\n * @var float\n */\n private $latitude;\n\n /**\n * @Field\n * @var float\n */\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"Now if you call ",(0,l.yg)("inlineCode",{parentName:"p"},"getCities()")," query you can pass the location input in the same way as with factories.\nThe ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with provided ",(0,l.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,l.yg)("inlineCode",{parentName:"p"},"longitude")," and passed to the controller as a parameter."),(0,l.yg)("p",null,"There are some important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"@Field")," annotation is recognized only on properties for Input Type."),(0,l.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,l.yg)("ul",{parentName:"li"},(0,l.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,l.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort."),(0,l.yg)("li",{parentName:"ul"},"For private or protected properties implemented public setter is required (if they are not set via constructor). For example ",(0,l.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),".")))),(0,l.yg)("h3",{id:"multiple-input-types-per-one-class"},"Multiple input types per one class"),(0,l.yg)("p",null,"Simple usage of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),' annotation on a class creates an GraphQl input named by class name + "Input" suffix if a class name does not end with it already.\nYou can add multiple ',(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotations to the same class, give them different names and link different fields.\nConsider the following example:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n #[Field]\n public ?int $age;\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Input(name="CreateUserInput", default=true)\n * @Input(name="UpdateUserInput", update=true)\n */\nclass UserInput\n{\n\n /**\n * @Field()\n * @var string\n */\n public $username;\n\n /**\n * @Field(for="CreateUserInput")\n * @var string\n */\n public string $email;\n\n /**\n * @Field(for="CreateUserInput", inputType="String!")\n * @Field(for="UpdateUserInput", inputType="String")\n * @var string|null\n */\n public $password;\n\n /**\n * @Field()\n * @var int|null\n */\n public $age;\n}\n')))),(0,l.yg)("p",null,"There are 2 input types created for just one class: ",(0,l.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,l.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,l.yg)("p",null,"Note that ",(0,l.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,l.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 7 they will be set to ",(0,l.yg)("inlineCode",{parentName:"p"},"null"),", while in PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/565a5567.7a63f34f.js b/assets/js/565a5567.42b506a7.js similarity index 97% rename from assets/js/565a5567.7a63f34f.js rename to assets/js/565a5567.42b506a7.js index 6753a66b1e..fceaa100bc 100644 --- a/assets/js/565a5567.7a63f34f.js +++ b/assets/js/565a5567.42b506a7.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5547],{86017:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},r=void 0,s={unversionedId:"doctrine-annotations-attributes",id:"version-4.0/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-4.0/doctrine_annotations_attributes.md",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/4.0/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/doctrine_annotations_attributes.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"}},l={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],u={toc:p},d="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(d,(0,a.A)({},u,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,i.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in GraphQLite 5.0"),(0,i.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support.\nThis was the purpose of the ',(0,i.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,i.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"Please note that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,i.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,i.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,i.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,i.yg)("div",{class:"alert alert--info"},(0,i.yg)("strong",null,"Heads up!"),"Some IDEs provide support for Doctrine annotations:",(0,i.yg)("ul",null,(0,i.yg)("li",null,"PhpStorm via the ",(0,i.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,i.yg)("li",null,"Eclipse via the ",(0,i.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,i.yg)("li",null,"Netbeans has native support")),(0,i.yg)("p",null,"We strongly recommend using an IDE that has Doctrine annotations support.")),(0,i.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,i.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,i.yg)("p",null,"The same code can be written this way:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,i.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,i.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,i.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,i.yg)("p",null,"They support the same attributes too."),(0,i.yg)("p",null,"A few notable differences:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,i.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,i.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,i.yg)("p",null,"Let's take an example with the ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/autowiring"},(0,i.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 7+")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 8")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5547],{86017:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},r=void 0,s={unversionedId:"doctrine-annotations-attributes",id:"version-4.0/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-4.0/doctrine_annotations_attributes.md",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/4.0/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/doctrine_annotations_attributes.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"}},l={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],u={toc:p},d="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(d,(0,a.A)({},u,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,i.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in GraphQLite 5.0"),(0,i.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support.\nThis was the purpose of the ',(0,i.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,i.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"Please note that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,i.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,i.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,i.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,i.yg)("div",{class:"alert alert--info"},(0,i.yg)("strong",null,"Heads up!"),"Some IDEs provide support for Doctrine annotations:",(0,i.yg)("ul",null,(0,i.yg)("li",null,"PhpStorm via the ",(0,i.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,i.yg)("li",null,"Eclipse via the ",(0,i.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,i.yg)("li",null,"Netbeans has native support")),(0,i.yg)("p",null,"We strongly recommend using an IDE that has Doctrine annotations support.")),(0,i.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,i.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,i.yg)("p",null,"The same code can be written this way:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,i.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,i.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,i.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,i.yg)("p",null,"They support the same attributes too."),(0,i.yg)("p",null,"A few notable differences:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,i.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,i.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,i.yg)("p",null,"Let's take an example with the ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/autowiring"},(0,i.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 7+")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 8")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/56af72f6.e2be7e89.js b/assets/js/56af72f6.9a096055.js similarity index 99% rename from assets/js/56af72f6.e2be7e89.js rename to assets/js/56af72f6.9a096055.js index 519f24db40..d6e09d38c2 100644 --- a/assets/js/56af72f6.e2be7e89.js +++ b/assets/js/56af72f6.9a096055.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4368],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),u=a(57485),s=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[u,s]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=u??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),h(e)}),[s,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:u,tabValues:s}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=s[a].value;n!==l&&(y(t),u(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},27809:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>u,default:()=>m,frontMatter:()=>l,metadata:()=>s,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},u=void 0,s={unversionedId:"custom-types",id:"version-6.0/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-6.0/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/6.0/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/custom-types.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"docs",previous:{title:"Pagination",permalink:"/docs/6.0/pagination"},next:{title:"Custom annotations",permalink:"/docs/6.0/field-middlewares"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4368],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),u=a(57485),s=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[u,s]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=u??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),h(e)}),[s,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:u,tabValues:s}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=s[a].value;n!==l&&(y(t),u(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},27809:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>u,default:()=>m,frontMatter:()=>l,metadata:()=>s,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},u=void 0,s={unversionedId:"custom-types",id:"version-6.0/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-6.0/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/6.0/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/custom-types.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"docs",previous:{title:"Pagination",permalink:"/docs/6.0/pagination"},next:{title:"Custom annotations",permalink:"/docs/6.0/field-middlewares"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5792f9ba.5dd35a6d.js b/assets/js/5792f9ba.782a6cc0.js similarity index 73% rename from assets/js/5792f9ba.5dd35a6d.js rename to assets/js/5792f9ba.782a6cc0.js index 9b40ca612e..25c76921a9 100644 --- a/assets/js/5792f9ba.5dd35a6d.js +++ b/assets/js/5792f9ba.782a6cc0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9606],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),r=n(96540),i=n(20053),o=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function p(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:n,groupId:a}),[d,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(d(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),n??t)})))}function b(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,a.A)({},e,t)),r.createElement(b,(0,a.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},18118:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var a=n(58168),r=(n(96540),n(15680)),i=(n(67443),n(11470)),o=n(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services",original_id:"autowiring"},s=void 0,u={unversionedId:"autowiring",id:"version-4.1/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-4.1/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/4.1/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/autowiring.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services",original_id:"autowiring"},sidebar:"version-4.1/docs",previous:{title:"Type mapping",permalink:"/docs/4.1/type_mapping"},next:{title:"Extending a type",permalink:"/docs/4.1/extend_type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null," #[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null," #[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend_type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9606],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),i=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(n.location.search);t.set(i,e),n.replace({...n.location,search:t.toString()})}),[i,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,i]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:n}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,t)),r.createElement(b,(0,n.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},18118:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),i=(a(67443),a(11470)),o=a(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services",original_id:"autowiring"},s=void 0,u={unversionedId:"autowiring",id:"version-4.1/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-4.1/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/4.1/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/autowiring.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services",original_id:"autowiring"},sidebar:"version-4.1/docs",previous:{title:"Type mapping",permalink:"/docs/4.1/type_mapping"},next:{title:"Extending a type",permalink:"/docs/4.1/extend_type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(h,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null," #[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null," #[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend_type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/579cc8d4.75c1db67.js b/assets/js/579cc8d4.3561f055.js similarity index 90% rename from assets/js/579cc8d4.75c1db67.js rename to assets/js/579cc8d4.3561f055.js index 05916ec99c..b370b0b4be 100644 --- a/assets/js/579cc8d4.75c1db67.js +++ b/assets/js/579cc8d4.3561f055.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3610],{55365:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>c});var i=n(58168),r=(n(96540),n(15680));n(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-3.0/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package",source:"@site/versioned_docs/version-3.0/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/3.0/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/implementing-security.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"}},u={},c=[],l={toc:c},g="wrapper";function h(e){let{components:t,...n}=e;return(0,r.yg)(g,(0,i.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package"),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3610],{55365:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>g,frontMatter:()=>a,metadata:()=>s,toc:()=>c});var i=n(58168),r=(n(96540),n(15680));n(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-3.0/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package",source:"@site/versioned_docs/version-3.0/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/3.0/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/implementing-security.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"}},u={},c=[],l={toc:c},h="wrapper";function g(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,i.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package"),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/57f5c28c.cedd9438.js b/assets/js/57f5c28c.1c46cd30.js similarity index 99% rename from assets/js/57f5c28c.cedd9438.js rename to assets/js/57f5c28c.1c46cd30.js index ed8c5cc019..882b80521f 100644 --- a/assets/js/57f5c28c.cedd9438.js +++ b/assets/js/57f5c28c.1c46cd30.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8372],{93300:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var r=a(58168),t=(a(96540),a(15680));a(67443);const o={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework",original_id:"other-frameworks"},i=void 0,l={unversionedId:"other-frameworks",id:"version-4.0/other-frameworks",title:"Getting started with any framework",description:"If you are using Symfony 4.x, checkout the Symfony bundle.",source:"@site/versioned_docs/version-4.0/other_frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/4.0/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/other_frameworks.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework",original_id:"other-frameworks"},sidebar:"version-4.0/docs",previous:{title:"Universal service providers",permalink:"/docs/4.0/universal_service_providers"},next:{title:"Queries",permalink:"/docs/4.0/queries"}},s={},p=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],c={toc:p},d="wrapper";function u(e){let{components:n,...o}=e;return(0,t.yg)(d,(0,r.A)({},c,o,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("p",null,"If you are using ",(0,t.yg)("strong",{parentName:"p"},"Symfony 4.x"),", checkout the ",(0,t.yg)("a",{parentName:"p",href:"/docs/4.0/symfony-bundle"},"Symfony bundle"),"."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'$builder->setUrl("/graphql"); // Modify the URL endpoint (defaults to /graphql)\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object. Use this object to configure Webonyx in details.\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n')),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"composer.json")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json"},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"index.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"config/container.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"src/Controllers/MyController.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"config/container.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."),(0,t.yg)("p",null,(0,t.yg)("img",{src:a(67258).A,width:"1132",height:"352"})))}u.isMDXComponent=!0},67258:(e,n,a)=>{a.d(n,{A:()=>r});const r=a.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8372],{93300:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var r=a(58168),t=(a(96540),a(15680));a(67443);const o={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework",original_id:"other-frameworks"},i=void 0,l={unversionedId:"other-frameworks",id:"version-4.0/other-frameworks",title:"Getting started with any framework",description:"If you are using Symfony 4.x, checkout the Symfony bundle.",source:"@site/versioned_docs/version-4.0/other_frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/4.0/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/other_frameworks.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework",original_id:"other-frameworks"},sidebar:"version-4.0/docs",previous:{title:"Universal service providers",permalink:"/docs/4.0/universal_service_providers"},next:{title:"Queries",permalink:"/docs/4.0/queries"}},s={},p=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],c={toc:p},d="wrapper";function u(e){let{components:n,...o}=e;return(0,t.yg)(d,(0,r.A)({},c,o,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("p",null,"If you are using ",(0,t.yg)("strong",{parentName:"p"},"Symfony 4.x"),", checkout the ",(0,t.yg)("a",{parentName:"p",href:"/docs/4.0/symfony-bundle"},"Symfony bundle"),"."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'$builder->setUrl("/graphql"); // Modify the URL endpoint (defaults to /graphql)\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object. Use this object to configure Webonyx in details.\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n')),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"composer.json")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json"},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"index.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"config/container.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"src/Controllers/MyController.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"config/container.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."),(0,t.yg)("p",null,(0,t.yg)("img",{src:a(67258).A,width:"1132",height:"352"})))}u.isMDXComponent=!0},67258:(e,n,a)=>{a.d(n,{A:()=>r});const r=a.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file diff --git a/assets/js/5881f7ec.fe052764.js b/assets/js/5881f7ec.cc785cb4.js similarity index 99% rename from assets/js/5881f7ec.fe052764.js rename to assets/js/5881f7ec.cc785cb4.js index 9f129f5840..cf18c9808b 100644 --- a/assets/js/5881f7ec.fe052764.js +++ b/assets/js/5881f7ec.cc785cb4.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8628],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),i=a(20053);const o={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,i.A)(o.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>N});var n=a(58168),i=a(96540),o=a(20053),r=a(23104),l=a(56347),s=a(57485),u=a(31682),d=a(89466);function p(e){return function(e){return i.Children.map(e,(e=>{if(!e||(0,i.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:i}}=e;return{value:t,label:a,attributes:n,default:i}}))}function c(e){const{values:t,children:a}=e;return(0,i.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,i.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function m(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=c(e),[r,l]=(0,i.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,u]=h({queryString:a,groupId:n}),[p,m]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,d.Dv)(a);return[n,(0,i.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),g=(()=>{const e=s??p;return y({value:e,tabValues:o})?e:null})();(0,i.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:r,selectValue:(0,i.useCallback)((e=>{if(!y({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),m(e)}),[u,m,o]),tabValues:o}}var g=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=d.indexOf(t),n=u[a].value;n!==l&&(p(t),s(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=d.indexOf(e.currentTarget)+1;t=d[a]??d[0];break}case"ArrowLeft":{const a=d.indexOf(e.currentTarget)-1;t=d[a]??d[d.length-1];break}}t?.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:r}=e;return i.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>d.push(e),onKeyDown:y,onClick:c},r,{className:(0,o.A)("tabs__item",v.tabItem,r?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,i.cloneElement)(e,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,i.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=m(e);return i.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},i.createElement(f,(0,n.A)({},e,t)),i.createElement(b,(0,n.A)({},e,t)))}function N(e){const t=(0,g.A)();return i.createElement(w,(0,n.A)({key:String(t)},e))}},18532:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=a(58168),i=(a(96540),a(15680)),o=(a(67443),a(11470)),r=a(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-7.0.0/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-7.0.0/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/validation.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"docs",previous:{title:"Error handling",permalink:"/docs/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/authentication-authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / subscription / factory ...",id:"using-the-validator-directly-on-a-query--mutation--subscription--factory-",level:3},{value:"Custom InputType Validation",id:"custom-inputtype-validation",level:2}],c={toc:p},y="wrapper";function h(e){let{components:t,...a}=e;return(0,i.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,i.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,i.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,i.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,i.yg)("a",{parentName:"p",href:"/docs/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,i.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,i.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,i.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,i.yg)("pre",{parentName:"li"},(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,i.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,i.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,i.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,i.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,i.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,i.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--subscription--factory-"},"Using the validator directly on a query / mutation / subscription / factory ..."),(0,i.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,i.yg)("p",null,"If the data entered by the user is ",(0,i.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,i.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\GraphQLite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,i.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,i.yg)("p",null,"You can also pass an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,i.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"),(0,i.yg)("h2",{id:"custom-inputtype-validation"},"Custom InputType Validation"),(0,i.yg)("p",null,"GraphQLite also supports a fully custom validation implementation for all input types defined with an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation or PHP8 ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute. This offers a way to validate input types before they're available as a method parameter of your query and mutation controllers. This way, when you're using your query or mutation controllers, you can feel confident that your input type objects have already been validated."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("p",null,"It's important to note that this validation implementation does not validate input types created with a factory. If you are creating an input type with a factory, or using primitive parameters in your query/mutation controllers, you should be sure to validate these independently. This is strictly for input type objects."),(0,i.yg)("p",null,"You can use one of the framework validation libraries listed above or implement your own validation for these cases. If you're using input type objects for most all of your query and mutation controllers, then there is little additional validation concerns with regards to user input. There are many reasons why you should consider defaulting to an InputType object, as opposed to individual arguments, for your queries and mutations. This is just one additional perk.")),(0,i.yg)("p",null,"To get started with validation on input types defined by an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation, you'll first need to register your validator with the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$factory = new SchemaFactory($cache, $this->container);\n$factory->addControllerNamespace('App\\\\Controllers');\n$factory->addTypeNamespace('App');\n// Register your validator\n$factory->setInputTypeValidator($this->container->get('your_validator'));\n$factory->createSchema();\n")),(0,i.yg)("p",null,"Your input type validator must implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Types\\InputTypeValidatorInterface"),", as shown below:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"interface InputTypeValidatorInterface\n{\n /**\n * Checks to see if the Validator is currently enabled.\n */\n public function isEnabled(): bool;\n\n /**\n * Performs the validation of the InputType.\n *\n * @param object $input The input type object to validate\n */\n public function validate(object $input): void;\n}\n")),(0,i.yg)("p",null,"The interface is quite simple. Handle all of your own validation logic in the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method. For example, you might use Symfony's annotation based validation in addition to some other custom validation logic. It's really up to you on how you wish to handle your own validation. The ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method will receive the input type object populated with the user input."),(0,i.yg)("p",null,"You'll notice that the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method has a ",(0,i.yg)("inlineCode",{parentName:"p"},"void")," return. The purpose here is to encourage you to throw an Exception or handle validation output however you best see fit. GraphQLite does it's best to stay out of your way and doesn't make attempts to handle validation output. You can, however, throw an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")," as usual (see ",(0,i.yg)("a",{parentName:"p",href:"error-handling"},"Error Handling")," for more details)."),(0,i.yg)("p",null,"Also available is the ",(0,i.yg)("inlineCode",{parentName:"p"},"isEnabled")," method. This method is checked before executing validation on an InputType being resolved. You can work out your own logic to selectively enable or disable validation through this method. In most cases, you can simply return ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," to keep it always enabled."),(0,i.yg)("p",null,"And that's it, now, anytime an input type is resolved, the validator will be executed on that input type immediately after it has been hydrated with user input."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8628],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),i=a(20053);const o={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,i.A)(o.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>N});var n=a(58168),i=a(96540),o=a(20053),r=a(23104),l=a(56347),s=a(57485),u=a(31682),d=a(89466);function p(e){return function(e){return i.Children.map(e,(e=>{if(!e||(0,i.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:i}}=e;return{value:t,label:a,attributes:n,default:i}}))}function c(e){const{values:t,children:a}=e;return(0,i.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,i.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function m(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=c(e),[r,l]=(0,i.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,u]=h({queryString:a,groupId:n}),[p,m]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,d.Dv)(a);return[n,(0,i.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),g=(()=>{const e=s??p;return y({value:e,tabValues:o})?e:null})();(0,i.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:r,selectValue:(0,i.useCallback)((e=>{if(!y({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),m(e)}),[u,m,o]),tabValues:o}}var g=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=d.indexOf(t),n=u[a].value;n!==l&&(p(t),s(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=d.indexOf(e.currentTarget)+1;t=d[a]??d[0];break}case"ArrowLeft":{const a=d.indexOf(e.currentTarget)-1;t=d[a]??d[d.length-1];break}}t?.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:r}=e;return i.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>d.push(e),onKeyDown:y,onClick:c},r,{className:(0,o.A)("tabs__item",v.tabItem,r?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,i.cloneElement)(e,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,i.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=m(e);return i.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},i.createElement(f,(0,n.A)({},e,t)),i.createElement(b,(0,n.A)({},e,t)))}function N(e){const t=(0,g.A)();return i.createElement(w,(0,n.A)({key:String(t)},e))}},18532:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=a(58168),i=(a(96540),a(15680)),o=(a(67443),a(11470)),r=a(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-7.0.0/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-7.0.0/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/validation.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"docs",previous:{title:"Error handling",permalink:"/docs/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/authentication-authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / subscription / factory ...",id:"using-the-validator-directly-on-a-query--mutation--subscription--factory-",level:3},{value:"Custom InputType Validation",id:"custom-inputtype-validation",level:2}],c={toc:p},y="wrapper";function h(e){let{components:t,...a}=e;return(0,i.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,i.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,i.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,i.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,i.yg)("a",{parentName:"p",href:"/docs/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,i.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,i.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,i.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,i.yg)("pre",{parentName:"li"},(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,i.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,i.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,i.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,i.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,i.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,i.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--subscription--factory-"},"Using the validator directly on a query / mutation / subscription / factory ..."),(0,i.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,i.yg)("p",null,"If the data entered by the user is ",(0,i.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,i.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\GraphQLite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,i.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,i.yg)("p",null,"You can also pass an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,i.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"),(0,i.yg)("h2",{id:"custom-inputtype-validation"},"Custom InputType Validation"),(0,i.yg)("p",null,"GraphQLite also supports a fully custom validation implementation for all input types defined with an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation or PHP8 ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute. This offers a way to validate input types before they're available as a method parameter of your query and mutation controllers. This way, when you're using your query or mutation controllers, you can feel confident that your input type objects have already been validated."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("p",null,"It's important to note that this validation implementation does not validate input types created with a factory. If you are creating an input type with a factory, or using primitive parameters in your query/mutation controllers, you should be sure to validate these independently. This is strictly for input type objects."),(0,i.yg)("p",null,"You can use one of the framework validation libraries listed above or implement your own validation for these cases. If you're using input type objects for most all of your query and mutation controllers, then there is little additional validation concerns with regards to user input. There are many reasons why you should consider defaulting to an InputType object, as opposed to individual arguments, for your queries and mutations. This is just one additional perk.")),(0,i.yg)("p",null,"To get started with validation on input types defined by an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation, you'll first need to register your validator with the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$factory = new SchemaFactory($cache, $this->container);\n$factory->addControllerNamespace('App\\\\Controllers');\n$factory->addTypeNamespace('App');\n// Register your validator\n$factory->setInputTypeValidator($this->container->get('your_validator'));\n$factory->createSchema();\n")),(0,i.yg)("p",null,"Your input type validator must implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Types\\InputTypeValidatorInterface"),", as shown below:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"interface InputTypeValidatorInterface\n{\n /**\n * Checks to see if the Validator is currently enabled.\n */\n public function isEnabled(): bool;\n\n /**\n * Performs the validation of the InputType.\n *\n * @param object $input The input type object to validate\n */\n public function validate(object $input): void;\n}\n")),(0,i.yg)("p",null,"The interface is quite simple. Handle all of your own validation logic in the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method. For example, you might use Symfony's annotation based validation in addition to some other custom validation logic. It's really up to you on how you wish to handle your own validation. The ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method will receive the input type object populated with the user input."),(0,i.yg)("p",null,"You'll notice that the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method has a ",(0,i.yg)("inlineCode",{parentName:"p"},"void")," return. The purpose here is to encourage you to throw an Exception or handle validation output however you best see fit. GraphQLite does it's best to stay out of your way and doesn't make attempts to handle validation output. You can, however, throw an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")," as usual (see ",(0,i.yg)("a",{parentName:"p",href:"error-handling"},"Error Handling")," for more details)."),(0,i.yg)("p",null,"Also available is the ",(0,i.yg)("inlineCode",{parentName:"p"},"isEnabled")," method. This method is checked before executing validation on an InputType being resolved. You can work out your own logic to selectively enable or disable validation through this method. In most cases, you can simply return ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," to keep it always enabled."),(0,i.yg)("p",null,"And that's it, now, anytime an input type is resolved, the validator will be executed on that input type immediately after it has been hydrated with user input."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/58d52345.a4dd140c.js b/assets/js/58d52345.77f848c4.js similarity index 87% rename from assets/js/58d52345.a4dd140c.js rename to assets/js/58d52345.77f848c4.js index 22dbb72165..e344a5267d 100644 --- a/assets/js/58d52345.a4dd140c.js +++ b/assets/js/58d52345.77f848c4.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[81],{69213:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>d});var r=a(58168),n=(a(96540),a(15680));a(67443);const i={id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},l=void 0,s={unversionedId:"features",id:"version-4.0/features",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-4.0/features.md",sourceDirName:".",slug:"/",permalink:"/docs/4.0/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/features.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},sidebar:"version-4.0/docs",next:{title:"Getting Started",permalink:"/docs/4.0/getting-started"}},o={},d=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],u={toc:d},p="wrapper";function c(e){let{components:t,...a}=e;return(0,n.yg)(p,(0,r.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,n.yg)("p",{align:"center"},(0,n.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,n.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,n.yg)("h2",{id:"features"},"Features"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,n.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,n.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,n.yg)("h2",{id:"basic-example"},"Basic example"),(0,n.yg)("p",null,"First, declare a query in your controller:"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,n.yg)("p",null,"Then, annotate the ",(0,n.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")),(0,n.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-grapql"},"{\n product(id: 42) {\n name\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[81],{69213:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>u});var r=a(58168),n=(a(96540),a(15680));a(67443);const i={id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},l=void 0,s={unversionedId:"features",id:"version-4.0/features",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-4.0/features.md",sourceDirName:".",slug:"/",permalink:"/docs/4.0/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/features.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},sidebar:"version-4.0/docs",next:{title:"Getting Started",permalink:"/docs/4.0/getting-started"}},o={},u=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:u},p="wrapper";function c(e){let{components:t,...a}=e;return(0,n.yg)(p,(0,r.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,n.yg)("p",{align:"center"},(0,n.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,n.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,n.yg)("h2",{id:"features"},"Features"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,n.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,n.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,n.yg)("h2",{id:"basic-example"},"Basic example"),(0,n.yg)("p",null,"First, declare a query in your controller:"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,n.yg)("p",null,"Then, annotate the ",(0,n.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")),(0,n.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-grapql"},"{\n product(id: 42) {\n name\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/58e6b30f.bba8a8b5.js b/assets/js/58e6b30f.cfedeae8.js similarity index 96% rename from assets/js/58e6b30f.bba8a8b5.js rename to assets/js/58e6b30f.cfedeae8.js index 2b1fcbcf62..faabf468e4 100644 --- a/assets/js/58e6b30f.bba8a8b5.js +++ b/assets/js/58e6b30f.cfedeae8.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9798],{81346:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>o,metadata:()=>r,toc:()=>d});var a=t(58168),i=(t(96540),t(15680));t(67443);const o={id:"external_type_declaration",title:"External type declaration",sidebar_label:"External type declaration",original_id:"external_type_declaration"},l=void 0,r={unversionedId:"external_type_declaration",id:"version-3.0/external_type_declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-3.0/external_type_declaration.mdx",sourceDirName:".",slug:"/external_type_declaration",permalink:"/docs/3.0/external_type_declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/external_type_declaration.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"external_type_declaration",title:"External type declaration",sidebar_label:"External type declaration",original_id:"external_type_declaration"},sidebar:"version-3.0/docs",previous:{title:"Authentication and authorization",permalink:"/docs/3.0/authentication_authorization"},next:{title:"Input types",permalink:"/docs/3.0/input-types"}},s={},d=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],p={toc:d},c="wrapper";function u(e){let{components:n,...t}=e;return(0,i.yg)(c,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,i.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,i.yg)("li",{parentName:"ul"},"etc.")),(0,i.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,i.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,i.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,i.yg)("p",null,"GraphQLite allows you to use a ",(0,i.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,i.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,i.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,i.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,i.yg)("p",null,"In methods with a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Field")," annotaiton, the first parameter is the ",(0,i.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,i.yg)("h2",{id:"sourcefield-annotation"},(0,i.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,i.yg)("p",null,"If you don't want to rewrite all ",(0,i.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')),(0,i.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,i.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,i.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,i.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"isName()"),")."),(0,i.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,i.yg)("p",null,"You may also check for logged users or users with a specific right using the ",(0,i.yg)("inlineCode",{parentName:"p"},"logged")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"right")," properties of the annotation:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", logged=true, right=@Right(name="CAN_ACCESS_Price"))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,i.yg)("p",null,"Just like the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"@Right")," annotations for regular fields, you can define a default value to use\nin case the user has insufficient permissions:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @SourceField(name="status", logged=true, right=@Right(name="CAN_ACCESS_STATUS"), failWith=null)\n */\n')),(0,i.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,i.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,i.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,i.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9798],{81346:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>o,metadata:()=>r,toc:()=>d});var a=t(58168),i=(t(96540),t(15680));t(67443);const o={id:"external_type_declaration",title:"External type declaration",sidebar_label:"External type declaration",original_id:"external_type_declaration"},l=void 0,r={unversionedId:"external_type_declaration",id:"version-3.0/external_type_declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-3.0/external_type_declaration.mdx",sourceDirName:".",slug:"/external_type_declaration",permalink:"/docs/3.0/external_type_declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/external_type_declaration.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"external_type_declaration",title:"External type declaration",sidebar_label:"External type declaration",original_id:"external_type_declaration"},sidebar:"version-3.0/docs",previous:{title:"Authentication and authorization",permalink:"/docs/3.0/authentication_authorization"},next:{title:"Input types",permalink:"/docs/3.0/input-types"}},s={},d=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],c={toc:d},p="wrapper";function u(e){let{components:n,...t}=e;return(0,i.yg)(p,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,i.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,i.yg)("li",{parentName:"ul"},"etc.")),(0,i.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,i.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,i.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,i.yg)("p",null,"GraphQLite allows you to use a ",(0,i.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,i.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,i.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,i.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,i.yg)("p",null,"In methods with a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Field")," annotaiton, the first parameter is the ",(0,i.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,i.yg)("h2",{id:"sourcefield-annotation"},(0,i.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,i.yg)("p",null,"If you don't want to rewrite all ",(0,i.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')),(0,i.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,i.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,i.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,i.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"isName()"),")."),(0,i.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,i.yg)("p",null,"You may also check for logged users or users with a specific right using the ",(0,i.yg)("inlineCode",{parentName:"p"},"logged")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"right")," properties of the annotation:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", logged=true, right=@Right(name="CAN_ACCESS_Price"))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,i.yg)("p",null,"Just like the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"@Right")," annotations for regular fields, you can define a default value to use\nin case the user has insufficient permissions:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @SourceField(name="status", logged=true, right=@Right(name="CAN_ACCESS_STATUS"), failWith=null)\n */\n')),(0,i.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,i.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,i.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,i.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5945e8b0.756ecc25.js b/assets/js/5945e8b0.0350a0ee.js similarity index 98% rename from assets/js/5945e8b0.756ecc25.js rename to assets/js/5945e8b0.0350a0ee.js index b11f2f74b6..92d7adfc52 100644 --- a/assets/js/5945e8b0.756ecc25.js +++ b/assets/js/5945e8b0.0350a0ee.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4773],{42056:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-6.0/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.0/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/6.0/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/symfony-bundle.md",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"docs",previous:{title:"Getting Started",permalink:"/docs/6.0/getting-started"},next:{title:"Laravel package",permalink:"/docs/6.0/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-6.0/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.0/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/6.0/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/symfony-bundle.md",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"docs",previous:{title:"Getting Started",permalink:"/docs/6.0/getting-started"},next:{title:"Laravel package",permalink:"/docs/6.0/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>c,frontMatter:()=>i,metadata:()=>o,toc:()=>d});var n=a(58168),r=(a(96540),a(15680));a(67443);const i={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},l=void 0,o={unversionedId:"index",id:"index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/docs/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/next/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/README.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"docs",next:{title:"Getting Started",permalink:"/docs/next/getting-started"}},s={},d=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],p={toc:d},u="wrapper";function c(e){let{components:t,...a}=e;return(0,r.yg)(u,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, subscriptions, mapping of arrays / iterators,\nfile uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4485],{25890:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>c,frontMatter:()=>i,metadata:()=>o,toc:()=>d});var n=a(58168),r=(a(96540),a(15680));a(67443);const i={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},l=void 0,o={unversionedId:"index",id:"index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/docs/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/next/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/README.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"docs",next:{title:"Getting Started",permalink:"/docs/next/getting-started"}},s={},d=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],p={toc:d},u="wrapper";function c(e){let{components:t,...a}=e;return(0,r.yg)(u,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, subscriptions, mapping of arrays / iterators,\nfile uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5a9b411c.633128b8.js b/assets/js/5a9b411c.41ceb8c0.js similarity index 96% rename from assets/js/5a9b411c.633128b8.js rename to assets/js/5a9b411c.41ceb8c0.js index 6b59ce23f9..7526029877 100644 --- a/assets/js/5a9b411c.633128b8.js +++ b/assets/js/5a9b411c.41ceb8c0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3693],{90052:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>l,default:()=>d,frontMatter:()=>o,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const o={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},l=void 0,i={unversionedId:"laravel-package",id:"version-4.0/laravel-package",title:"Getting started with Laravel",description:"The GraphQLite-Laravel package is compatible with Laravel 5.7+ and Laravel 6.x.",source:"@site/versioned_docs/version-4.0/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/4.0/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/laravel-package.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},sidebar:"version-4.0/docs",previous:{title:"Symfony bundle",permalink:"/docs/4.0/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/4.0/universal_service_providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function d(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ php artisan vendor:publish --provider=TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider\n")),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"config/graphqlite.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"config/graphqlite.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Sample Apollo client setup with CSRF support")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3693],{90052:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},o=void 0,i={unversionedId:"laravel-package",id:"version-4.0/laravel-package",title:"Getting started with Laravel",description:"The GraphQLite-Laravel package is compatible with Laravel 5.7+ and Laravel 6.x.",source:"@site/versioned_docs/version-4.0/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/4.0/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/laravel-package.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},sidebar:"version-4.0/docs",previous:{title:"Symfony bundle",permalink:"/docs/4.0/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/4.0/universal_service_providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function d(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ php artisan vendor:publish --provider=TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider\n")),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"config/graphqlite.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"config/graphqlite.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Sample Apollo client setup with CSRF support")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5bc7272e.2082701e.js b/assets/js/5bc7272e.2ea91e98.js similarity index 98% rename from assets/js/5bc7272e.2082701e.js rename to assets/js/5bc7272e.2ea91e98.js index 475b3fd2fe..0bb7b04686 100644 --- a/assets/js/5bc7272e.2082701e.js +++ b/assets/js/5bc7272e.2ea91e98.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9454],{7061:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating"},r=void 0,l={unversionedId:"migrating",id:"version-7.0.0/migrating",title:"Migrating",description:"Migrating from v4.0 to v4.1",source:"@site/versioned_docs/version-7.0.0/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/migrating.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating"},sidebar:"docs",previous:{title:"Troubleshooting",permalink:"/docs/troubleshooting"},next:{title:"Annotations VS Attributes",permalink:"/docs/doctrine-annotations-attributes"}},s={},g=[{value:"Migrating from v4.0 to v4.1",id:"migrating-from-v40-to-v41",level:2},{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},p="wrapper";function u(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v40-to-v41"},"Migrating from v4.0 to v4.1"),(0,i.yg)("p",null,"GraphQLite follows Semantic Versioning. GraphQLite 4.1 is backward compatible with GraphQLite 4.0. See\n",(0,i.yg)("a",{parentName:"p",href:"/docs/semver"},"semantic versioning")," for more details."),(0,i.yg)("p",null,"There is one exception though: the ",(0,i.yg)("strong",{parentName:"p"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL\ninput types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"p"},"composer.json")," by running this command:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/annotations-reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9454],{7061:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating"},r=void 0,l={unversionedId:"migrating",id:"version-7.0.0/migrating",title:"Migrating",description:"Migrating from v4.0 to v4.1",source:"@site/versioned_docs/version-7.0.0/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/migrating.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating"},sidebar:"docs",previous:{title:"Troubleshooting",permalink:"/docs/troubleshooting"},next:{title:"Annotations VS Attributes",permalink:"/docs/doctrine-annotations-attributes"}},s={},g=[{value:"Migrating from v4.0 to v4.1",id:"migrating-from-v40-to-v41",level:2},{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},p="wrapper";function u(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v40-to-v41"},"Migrating from v4.0 to v4.1"),(0,i.yg)("p",null,"GraphQLite follows Semantic Versioning. GraphQLite 4.1 is backward compatible with GraphQLite 4.0. See\n",(0,i.yg)("a",{parentName:"p",href:"/docs/semver"},"semantic versioning")," for more details."),(0,i.yg)("p",null,"There is one exception though: the ",(0,i.yg)("strong",{parentName:"p"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL\ninput types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"p"},"composer.json")," by running this command:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/annotations-reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5d7590c2.400b4369.js b/assets/js/5d7590c2.f1875551.js similarity index 90% rename from assets/js/5d7590c2.400b4369.js rename to assets/js/5d7590c2.f1875551.js index 81b95a2af6..f587373ebc 100644 --- a/assets/js/5d7590c2.400b4369.js +++ b/assets/js/5d7590c2.f1875551.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9336],{43268:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>p});var n=a(58168),i=(a(96540),a(15680));a(67443);const r={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},o=void 0,s={unversionedId:"pagination",id:"pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/docs/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/next/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/pagination.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},sidebar:"docs",previous:{title:"File uploads",permalink:"/docs/next/file-uploads"},next:{title:"Custom types",permalink:"/docs/next/custom-types"}},l={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],u={toc:p},g="wrapper";function d(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,i.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,i.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,i.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,i.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,i.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,i.yg)("h2",{id:"installation"},"Installation"),(0,i.yg)("p",null,"You will need to install the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,i.yg)("h2",{id:"usage"},"Usage"),(0,i.yg)("p",null,"In your query, simply return a class that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")),(0,i.yg)("p",null,"Notice that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,i.yg)("li",{parentName:"ul"},"you MUST add a ",(0,i.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,i.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,i.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,i.yg)("p",null,'The "count" field returns the ',(0,i.yg)("strong",{parentName:"p"},"total count")," of items."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9336],{43268:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var n=a(58168),i=(a(96540),a(15680));a(67443);const r={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},l=void 0,o={unversionedId:"pagination",id:"pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/docs/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/next/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/pagination.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},sidebar:"docs",previous:{title:"File uploads",permalink:"/docs/next/file-uploads"},next:{title:"Custom types",permalink:"/docs/next/custom-types"}},s={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],u={toc:p},g="wrapper";function d(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,i.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,i.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,i.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,i.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,i.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,i.yg)("h2",{id:"installation"},"Installation"),(0,i.yg)("p",null,"You will need to install the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,i.yg)("h2",{id:"usage"},"Usage"),(0,i.yg)("p",null,"In your query, simply return a class that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")),(0,i.yg)("p",null,"Notice that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,i.yg)("li",{parentName:"ul"},"you MUST add a ",(0,i.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,i.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,i.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,i.yg)("p",null,'The "count" field returns the ',(0,i.yg)("strong",{parentName:"p"},"total count")," of items."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5dde70bf.f04f0102.js b/assets/js/5dde70bf.05ad436e.js similarity index 98% rename from assets/js/5dde70bf.f04f0102.js rename to assets/js/5dde70bf.05ad436e.js index 6498cf5e34..5ecd011d15 100644 --- a/assets/js/5dde70bf.f04f0102.js +++ b/assets/js/5dde70bf.05ad436e.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[288],{73287:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-7.0.0/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-7.0.0/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/symfony-bundle.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"docs",previous:{title:"Getting Started",permalink:"/docs/getting-started"},next:{title:"Laravel package",permalink:"/docs/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>g,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},l=void 0,o={unversionedId:"symfony-bundle",id:"version-7.0.0/symfony-bundle",title:"Getting started with Symfony",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-7.0.0/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/symfony-bundle.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle"},sidebar:"docs",previous:{title:"Getting Started",permalink:"/docs/getting-started"},next:{title:"Laravel package",permalink:"/docs/laravel-package"}},s={},p=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],c={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(d,(0,a.A)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,i.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,i.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,i.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,i.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,i.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,i.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Now, go to the ",(0,i.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-yaml",metastring:'title="config/packages/graphqlite.yaml"',title:'"config/packages/graphqlite.yaml"'},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,i.yg)("p",null,"More advanced parameters are detailed in the ",(0,i.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,i.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,i.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,i.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,i.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="app/AppKernel.php"',title:'"app/AppKernel.php"'},"{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"version-4.2/laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.2/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/4.2/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/laravel-package.md",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"version-4.2/docs",previous:{title:"Symfony bundle",permalink:"/docs/4.2/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/4.2/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ php artisan vendor:publish --provider=TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider\n")),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2347],{83162:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"version-4.2/laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.2/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/4.2/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/laravel-package.md",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"version-4.2/docs",previous:{title:"Symfony bundle",permalink:"/docs/4.2/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/4.2/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ php artisan vendor:publish --provider=TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider\n")),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5e352ef4.44328195.js b/assets/js/5e352ef4.0d71c311.js similarity index 99% rename from assets/js/5e352ef4.44328195.js rename to assets/js/5e352ef4.0d71c311.js index 4ec6162d97..c1fc396997 100644 --- a/assets/js/5e352ef4.44328195.js +++ b/assets/js/5e352ef4.0d71c311.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[104],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>N});var n=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),d=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function c(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??p(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function m(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,l.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!m({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=y({queryString:t,groupId:n}),[p,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,d.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),g=(()=>{const e=s??p;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),h(e)}),[u,h,i]),tabValues:i}}var g=t(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:a,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),c=e=>{const a=e.currentTarget,t=d.indexOf(a),n=u[t].value;n!==l&&(p(a),s(n))},m=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const t=d.indexOf(e.currentTarget)+1;a=d[t]??d[0];break}case"ArrowLeft":{const t=d.indexOf(e.currentTarget)-1;a=d[t]??d[d.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===a?0:-1,"aria-selected":l===a,key:a,ref:e=>d.push(e),onKeyDown:m,onClick:c},o,{className:(0,i.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=h(e);return r.createElement("div",{className:(0,i.A)("tabs-container",v.tabList)},r.createElement(f,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function N(e){const a=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},49786:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>d,contentTitle:()=>s,default:()=>y,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-3.0/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-3.0/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/3.0/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/validation.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],c={toc:p},m="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(m,(0,n.A)({},c,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,r.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,r.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,r.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,r.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,r.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box"),(0,r.yg)("li",{parentName:"ul"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,r.yg)("em",{parentName:"li"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:",(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,r.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,r.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,r.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"UserController.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"UserController.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"User.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"User.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,r.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,r.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,r.yg)("p",null,"If the data entered by the user is ",(0,r.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,r.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\Graphqlite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,r.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,r.yg)("p",null,"You can also pass an array to the ",(0,r.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,r.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[104],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>N});var n=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),d=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function c(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??p(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function m(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,l.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!m({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=y({queryString:t,groupId:n}),[p,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,d.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),g=(()=>{const e=s??p;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),h(e)}),[u,h,i]),tabValues:i}}var g=t(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:a,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),c=e=>{const a=e.currentTarget,t=d.indexOf(a),n=u[t].value;n!==l&&(p(a),s(n))},m=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const t=d.indexOf(e.currentTarget)+1;a=d[t]??d[0];break}case"ArrowLeft":{const t=d.indexOf(e.currentTarget)-1;a=d[t]??d[d.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===a?0:-1,"aria-selected":l===a,key:a,ref:e=>d.push(e),onKeyDown:m,onClick:c},o,{className:(0,i.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=h(e);return r.createElement("div",{className:(0,i.A)("tabs-container",v.tabList)},r.createElement(f,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function N(e){const a=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},49786:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>d,contentTitle:()=>s,default:()=>y,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-3.0/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-3.0/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/3.0/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/validation.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],c={toc:p},m="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(m,(0,n.A)({},c,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,r.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,r.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,r.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,r.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,r.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box"),(0,r.yg)("li",{parentName:"ul"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,r.yg)("em",{parentName:"li"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:",(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,r.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,r.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,r.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"UserController.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"UserController.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"User.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"User.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,r.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,r.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,r.yg)("p",null,"If the data entered by the user is ",(0,r.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,r.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\Graphqlite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,r.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,r.yg)("p",null,"You can also pass an array to the ",(0,r.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,r.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5e457383.2cb59d7a.js b/assets/js/5e457383.77ecbaa1.js similarity index 97% rename from assets/js/5e457383.2cb59d7a.js rename to assets/js/5e457383.77ecbaa1.js index 5102b055fc..759d5b1dec 100644 --- a/assets/js/5e457383.2cb59d7a.js +++ b/assets/js/5e457383.77ecbaa1.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3457],{42799:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-7.0.0/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/versioned_docs/version-7.0.0/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/semver.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"docs",previous:{title:"Annotations reference",permalink:"/docs/annotations-reference"},next:{title:"Changelog",permalink:"/docs/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3457],{42799:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-7.0.0/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/versioned_docs/version-7.0.0/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/semver.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"docs",previous:{title:"Annotations reference",permalink:"/docs/annotations-reference"},next:{title:"Changelog",permalink:"/docs/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5ec7a7fe.486c187f.js b/assets/js/5ec7a7fe.825af4e3.js similarity index 99% rename from assets/js/5ec7a7fe.486c187f.js rename to assets/js/5ec7a7fe.825af4e3.js index 7ae567157e..cd72c84c5a 100644 --- a/assets/js/5ec7a7fe.486c187f.js +++ b/assets/js/5ec7a7fe.825af4e3.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2676],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},40631:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},s=void 0,u={unversionedId:"extend-type",id:"version-6.0/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-6.0/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/6.0/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/extend-type.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"docs",previous:{title:"Autowiring services",permalink:"/docs/6.0/autowiring"},next:{title:"External type declaration",permalink:"/docs/6.0/external-type-declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2676],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},40631:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},s=void 0,u={unversionedId:"extend-type",id:"version-6.0/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-6.0/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/6.0/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/extend-type.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"docs",previous:{title:"Autowiring services",permalink:"/docs/6.0/autowiring"},next:{title:"External type declaration",permalink:"/docs/6.0/external-type-declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5ececfab.951bf2be.js b/assets/js/5ececfab.010d8af8.js similarity index 99% rename from assets/js/5ececfab.951bf2be.js rename to assets/js/5ececfab.010d8af8.js index 9b4bd6ff76..0c3a34f988 100644 --- a/assets/js/5ececfab.951bf2be.js +++ b/assets/js/5ececfab.010d8af8.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2275],{19365:(e,n,a)=>{a.d(n,{A:()=>r});var t=a(96540),p=a(20053);const l={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:a,className:r}=e;return t.createElement("div",{role:"tabpanel",className:(0,p.A)(l.tabItem,r),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>N});var t=a(58168),p=a(96540),l=a(20053),r=a(23104),s=a(56347),i=a(57485),u=a(31682),o=a(89466);function c(e){return function(e){return p.Children.map(e,(e=>{if(!e||(0,p.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:p}}=e;return{value:n,label:a,attributes:t,default:p}}))}function y(e){const{values:n,children:a}=e;return(0,p.useMemo)((()=>{const e=n??c(a);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function m(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function d(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,i.aZ)(l),(0,p.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function g(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=y(e),[r,s]=(0,p.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[i,u]=d({queryString:a,groupId:t}),[c,g]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,p.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=i??c;return m({value:e,tabValues:l})?e:null})();(0,p.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:r,selectValue:(0,p.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const o=[],{blockElementScrollPositionUntilNextRender:c}=(0,r.a_)(),y=e=>{const n=e.currentTarget,a=o.indexOf(n),t=u[a].value;t!==s&&(c(n),i(t))},m=e=>{let n=null;switch(e.key){case"Enter":y(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return p.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},u.map((e=>{let{value:n,label:a,attributes:r}=e;return p.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:m,onClick:y},r,{className:(0,l.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,p.cloneElement)(e,{className:"margin-top--md"}):null}return p.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,p.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function T(e){const n=g(e);return p.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},p.createElement(b,(0,t.A)({},e,n)),p.createElement(v,(0,t.A)({},e,n)))}function N(e){const n=(0,h.A)();return p.createElement(T,(0,t.A)({key:String(n)},e))}},61314:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>u,toc:()=>c});var t=a(58168),p=(a(96540),a(15680)),l=(a(67443),a(11470)),r=a(19365);const s={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},i=void 0,u={unversionedId:"type-mapping",id:"version-4.3/type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-4.3/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/4.3/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/type-mapping.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"version-4.3/docs",previous:{title:"Mutations",permalink:"/docs/4.3/mutations"},next:{title:"Autowiring services",permalink:"/docs/4.3/autowiring"}},o={},c=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],y={toc:c},m="wrapper";function d(e){let{components:n,...a}=e;return(0,p.yg)(m,(0,t.A)({},y,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/4.3/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(name="MyProduct")\n */\nclass Product { /* ... */ }\n')))),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')))),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')))),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/4.3/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")))),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")))),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")))),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"You can create a GraphQL union type ",(0,p.yg)("em",{parentName:"p"},"on the fly")," using the pipe ",(0,p.yg)("inlineCode",{parentName:"p"},"|")," operator in the PHPDoc:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Company|Contact <== can return a company OR a contact.\n */\n#[Query]\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact <== can return a company OR a contact.\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")))),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,"PHP has no native support for enum types. Hopefully, there are a number of PHP libraries that emulate enums in PHP.\nThe most commonly used library is ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum")," and GraphQLite comes with\nnative support for it."),(0,p.yg)("p",null,"You will first need to install ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[]\n */\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')))),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n/**\n * @EnumType(name="UserStatus")\n */\nclass StatusEnum extends Enum\n{\n // ...\n}\n')))),(0,p.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,p.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,p.yg)("div",{class:"alert alert--info"},'There are many enumeration library in PHP and you might be using another library. If you want to add support for your own library, this is not extremely difficult to do. You need to register a custom "RootTypeMapper" with GraphQLite. You can learn more about ',(0,p.yg)("em",null,"type mappers")," in the ",(0,p.yg)("a",{href:"internals"},'"internals" documentation'),"and ",(0,p.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/MyCLabsEnumTypeMapper.php"},"copy/paste and adapt the root type mapper used for myclabs/php-enum"),"."),(0,p.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,p.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n * @deprecated use field `name` instead\n */\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,p.yg)("p",null,"This will add the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,p.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,p.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,p.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,p.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,p.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2275],{19365:(e,n,a)=>{a.d(n,{A:()=>r});var t=a(96540),p=a(20053);const l={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:a,className:r}=e;return t.createElement("div",{role:"tabpanel",className:(0,p.A)(l.tabItem,r),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>N});var t=a(58168),p=a(96540),l=a(20053),r=a(23104),s=a(56347),i=a(57485),u=a(31682),o=a(89466);function c(e){return function(e){return p.Children.map(e,(e=>{if(!e||(0,p.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:p}}=e;return{value:n,label:a,attributes:t,default:p}}))}function y(e){const{values:n,children:a}=e;return(0,p.useMemo)((()=>{const e=n??c(a);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function m(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function d(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,i.aZ)(l),(0,p.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function g(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=y(e),[r,s]=(0,p.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[i,u]=d({queryString:a,groupId:t}),[c,g]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,p.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=i??c;return m({value:e,tabValues:l})?e:null})();(0,p.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:r,selectValue:(0,p.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const o=[],{blockElementScrollPositionUntilNextRender:c}=(0,r.a_)(),y=e=>{const n=e.currentTarget,a=o.indexOf(n),t=u[a].value;t!==s&&(c(n),i(t))},m=e=>{let n=null;switch(e.key){case"Enter":y(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return p.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},u.map((e=>{let{value:n,label:a,attributes:r}=e;return p.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:m,onClick:y},r,{className:(0,l.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,p.cloneElement)(e,{className:"margin-top--md"}):null}return p.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,p.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function T(e){const n=g(e);return p.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},p.createElement(b,(0,t.A)({},e,n)),p.createElement(v,(0,t.A)({},e,n)))}function N(e){const n=(0,h.A)();return p.createElement(T,(0,t.A)({key:String(n)},e))}},61314:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>u,toc:()=>c});var t=a(58168),p=(a(96540),a(15680)),l=(a(67443),a(11470)),r=a(19365);const s={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},i=void 0,u={unversionedId:"type-mapping",id:"version-4.3/type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-4.3/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/4.3/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/type-mapping.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"version-4.3/docs",previous:{title:"Mutations",permalink:"/docs/4.3/mutations"},next:{title:"Autowiring services",permalink:"/docs/4.3/autowiring"}},o={},c=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],y={toc:c},m="wrapper";function d(e){let{components:n,...a}=e;return(0,p.yg)(m,(0,t.A)({},y,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/4.3/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(name="MyProduct")\n */\nclass Product { /* ... */ }\n')))),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')))),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')))),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/4.3/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")))),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")))),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")))),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"You can create a GraphQL union type ",(0,p.yg)("em",{parentName:"p"},"on the fly")," using the pipe ",(0,p.yg)("inlineCode",{parentName:"p"},"|")," operator in the PHPDoc:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Company|Contact <== can return a company OR a contact.\n */\n#[Query]\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact <== can return a company OR a contact.\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")))),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,"PHP has no native support for enum types. Hopefully, there are a number of PHP libraries that emulate enums in PHP.\nThe most commonly used library is ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum")," and GraphQLite comes with\nnative support for it."),(0,p.yg)("p",null,"You will first need to install ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[]\n */\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')))),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n/**\n * @EnumType(name="UserStatus")\n */\nclass StatusEnum extends Enum\n{\n // ...\n}\n')))),(0,p.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,p.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,p.yg)("div",{class:"alert alert--info"},'There are many enumeration library in PHP and you might be using another library. If you want to add support for your own library, this is not extremely difficult to do. You need to register a custom "RootTypeMapper" with GraphQLite. You can learn more about ',(0,p.yg)("em",null,"type mappers")," in the ",(0,p.yg)("a",{href:"internals"},'"internals" documentation'),"and ",(0,p.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/MyCLabsEnumTypeMapper.php"},"copy/paste and adapt the root type mapper used for myclabs/php-enum"),"."),(0,p.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,p.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n * @deprecated use field `name` instead\n */\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,p.yg)("p",null,"This will add the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,p.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,p.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,p.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,p.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,p.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5fa4a5b6.58928c34.js b/assets/js/5fa4a5b6.0b6f7acb.js similarity index 99% rename from assets/js/5fa4a5b6.58928c34.js rename to assets/js/5fa4a5b6.0b6f7acb.js index 3f13807dac..bd7cc61323 100644 --- a/assets/js/5fa4a5b6.58928c34.js +++ b/assets/js/5fa4a5b6.0b6f7acb.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3355],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},42462:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features",original_id:"symfony-bundle-advanced"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-4.1/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony.",source:"@site/versioned_docs/version-4.1/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/4.1/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/symfony-bundle-advanced.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features",original_id:"symfony-bundle-advanced"},sidebar:"version-4.1/docs",previous:{title:"Class with multiple output types",permalink:"/docs/4.1/multiple_output_types"},next:{title:"Laravel specific features",permalink:"/docs/4.1/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3355],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},42462:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features",original_id:"symfony-bundle-advanced"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-4.1/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony.",source:"@site/versioned_docs/version-4.1/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/4.1/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/symfony-bundle-advanced.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features",original_id:"symfony-bundle-advanced"},sidebar:"version-4.1/docs",previous:{title:"Class with multiple output types",permalink:"/docs/4.1/multiple_output_types"},next:{title:"Laravel specific features",permalink:"/docs/4.1/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5ffc8074.c7c46b28.js b/assets/js/5ffc8074.f8c76d92.js similarity index 99% rename from assets/js/5ffc8074.c7c46b28.js rename to assets/js/5ffc8074.f8c76d92.js index edae134b2b..397baa681d 100644 --- a/assets/js/5ffc8074.c7c46b28.js +++ b/assets/js/5ffc8074.f8c76d92.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4122],{78377:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>g,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=n(58168),l=(n(96540),n(15680));n(67443);const r={id:"annotations_reference",title:"Annotations reference",sidebar_label:"Annotations reference",original_id:"annotations_reference"},i=void 0,o={unversionedId:"annotations_reference",id:"version-3.0/annotations_reference",title:"Annotations reference",description:"@Query annotation",source:"@site/versioned_docs/version-3.0/annotations_reference.md",sourceDirName:".",slug:"/annotations_reference",permalink:"/docs/3.0/annotations_reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/annotations_reference.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"annotations_reference",title:"Annotations reference",sidebar_label:"Annotations reference",original_id:"annotations_reference"},sidebar:"version-3.0/docs",previous:{title:"Troubleshooting",permalink:"/docs/3.0/troubleshooting"}},g={},p=[{value:"@Query annotation",id:"query-annotation",level:2},{value:"@Mutation annotation",id:"mutation-annotation",level:2},{value:"@Type annotation",id:"type-annotation",level:2},{value:"@ExtendType annotation",id:"extendtype-annotation",level:2},{value:"@Field annotation",id:"field-annotation",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@Logged annotation",id:"logged-annotation",level:2},{value:"@Right annotation",id:"right-annotation",level:2},{value:"@FailWith annotation",id:"failwith-annotation",level:2},{value:"@Factory annotation",id:"factory-annotation",level:2}],y={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,l.yg)(d,(0,a.A)({},y,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("h2",{id:"query-annotation"},"@Query annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/type_mapping"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation-annotation"},"@Mutation annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/type_mapping"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type-annotation"},"@Type annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, ',(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/external_type_declaration"},"the class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," is a service"),".")))),(0,l.yg)("h2",{id:"extendtype-annotation"},"@ExtendType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/extend_type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")))),(0,l.yg)("h2",{id:"field-annotation"},"@Field annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/type_mapping"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"sourcefield-annotation"},"@SourceField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/type_mapping"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"logged"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether the user must be logged or not to see the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"right"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"Right annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"The right the user must have to see the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"failWith"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"A value to return if the user is not authorized to see the field. If not specified, the field will not be visible at all to the user.")))),(0,l.yg)("h2",{id:"logged-annotation"},"@Logged annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right-annotation"},"@Right annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith-annotation"},"@FailWith annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"factory-annotation"},"@Factory annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4122],{78377:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>g,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=n(58168),l=(n(96540),n(15680));n(67443);const r={id:"annotations_reference",title:"Annotations reference",sidebar_label:"Annotations reference",original_id:"annotations_reference"},i=void 0,o={unversionedId:"annotations_reference",id:"version-3.0/annotations_reference",title:"Annotations reference",description:"@Query annotation",source:"@site/versioned_docs/version-3.0/annotations_reference.md",sourceDirName:".",slug:"/annotations_reference",permalink:"/docs/3.0/annotations_reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/annotations_reference.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"annotations_reference",title:"Annotations reference",sidebar_label:"Annotations reference",original_id:"annotations_reference"},sidebar:"version-3.0/docs",previous:{title:"Troubleshooting",permalink:"/docs/3.0/troubleshooting"}},g={},p=[{value:"@Query annotation",id:"query-annotation",level:2},{value:"@Mutation annotation",id:"mutation-annotation",level:2},{value:"@Type annotation",id:"type-annotation",level:2},{value:"@ExtendType annotation",id:"extendtype-annotation",level:2},{value:"@Field annotation",id:"field-annotation",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@Logged annotation",id:"logged-annotation",level:2},{value:"@Right annotation",id:"right-annotation",level:2},{value:"@FailWith annotation",id:"failwith-annotation",level:2},{value:"@Factory annotation",id:"factory-annotation",level:2}],y={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,l.yg)(d,(0,a.A)({},y,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("h2",{id:"query-annotation"},"@Query annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/type_mapping"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation-annotation"},"@Mutation annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/type_mapping"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type-annotation"},"@Type annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, ',(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/external_type_declaration"},"the class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," is a service"),".")))),(0,l.yg)("h2",{id:"extendtype-annotation"},"@ExtendType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/extend_type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")))),(0,l.yg)("h2",{id:"field-annotation"},"@Field annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/type_mapping"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"sourcefield-annotation"},"@SourceField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/3.0/type_mapping"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"logged"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether the user must be logged or not to see the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"right"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"Right annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"The right the user must have to see the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"failWith"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"A value to return if the user is not authorized to see the field. If not specified, the field will not be visible at all to the user.")))),(0,l.yg)("h2",{id:"logged-annotation"},"@Logged annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right-annotation"},"@Right annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith-annotation"},"@FailWith annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"factory-annotation"},"@Factory annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/606959d6.3edb8139.js b/assets/js/606959d6.68fabfee.js similarity index 93% rename from assets/js/606959d6.3edb8139.js rename to assets/js/606959d6.68fabfee.js index 75d6e579c1..4639717acc 100644 --- a/assets/js/606959d6.3edb8139.js +++ b/assets/js/606959d6.68fabfee.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[862],{19365:(e,a,n)=>{n.d(a,{A:()=>i});var t=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:n,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},a)}},11470:(e,a,n)=>{n.d(a,{A:()=>P});var t=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:n,attributes:t,default:r}}=e;return{value:a,label:n,attributes:t,default:r}}))}function c(e){const{values:a,children:n}=e;return(0,r.useMemo)((()=>{const e=a??d(n);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,n])}function g(e){let{value:a,tabValues:n}=e;return n.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:n}=e;const t=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:n}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:a,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(t.location.search);a.set(l,e),t.replace({...t.location,search:a.toString()})}),[l,t])]}function m(e){const{defaultValue:a,queryString:n=!1,groupId:t}=e,l=c(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const t=n.find((e=>e.default))??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:n,groupId:t}),[d,m]=function(e){let{groupId:a}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(a),[t,l]=(0,p.Dv)(n);return[t,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:t}),y=(()=>{const e=s??d;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:d}=(0,i.a_)(),c=e=>{const a=e.currentTarget,n=p.indexOf(a),t=u[n].value;t!==o&&(d(a),s(t))},g=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;a=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;a=p[n]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},a)},u.map((e=>{let{value:a,label:n,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:c},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),n??a)})))}function b(e){let{lazy:a,children:n,selectedValue:t}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==t}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,t.A)({},e,a)),r.createElement(b,(0,t.A)({},e,a)))}function P(e){const a=(0,y.A)();return r.createElement(w,(0,t.A)({key:String(a)},e))}},4531:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>d});var t=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-4.2/laravel-package-advanced",title:"Laravel package: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.2/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/4.2/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/laravel-package-advanced.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},sidebar:"version-4.2/docs",previous:{title:"Symfony specific features",permalink:"/docs/4.2/symfony-bundle-advanced"},next:{title:"Internals",permalink:"/docs/4.2/internals"}},p={},d=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3}],c={toc:d},g="wrapper";function h(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},c,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n'))))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[862],{19365:(e,a,n)=>{n.d(a,{A:()=>i});var t=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:n,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},a)}},11470:(e,a,n)=>{n.d(a,{A:()=>P});var t=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:n,attributes:t,default:r}}=e;return{value:a,label:n,attributes:t,default:r}}))}function d(e){const{values:a,children:n}=e;return(0,r.useMemo)((()=>{const e=a??c(n);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,n])}function g(e){let{value:a,tabValues:n}=e;return n.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:n}=e;const t=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:n}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:a,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(t.location.search);a.set(l,e),t.replace({...t.location,search:a.toString()})}),[l,t])]}function m(e){const{defaultValue:a,queryString:n=!1,groupId:t}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const t=n.find((e=>e.default))??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:n,groupId:t}),[c,m]=function(e){let{groupId:a}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(a),[t,l]=(0,p.Dv)(n);return[t,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:t}),y=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const a=e.currentTarget,n=p.indexOf(a),t=u[n].value;t!==o&&(c(a),s(t))},g=e=>{let a=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;a=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;a=p[n]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},a)},u.map((e=>{let{value:a,label:n,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),n??a)})))}function b(e){let{lazy:a,children:n,selectedValue:t}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==t}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,t.A)({},e,a)),r.createElement(b,(0,t.A)({},e,a)))}function P(e){const a=(0,y.A)();return r.createElement(w,(0,t.A)({key:String(a)},e))}},4531:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var t=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-4.2/laravel-package-advanced",title:"Laravel package: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.2/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/4.2/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/laravel-package-advanced.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},sidebar:"version-4.2/docs",previous:{title:"Symfony specific features",permalink:"/docs/4.2/symfony-bundle-advanced"},next:{title:"Internals",permalink:"/docs/4.2/internals"}},p={},c=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3}],d={toc:c},g="wrapper";function h(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n'))))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/60d99771.af816a56.js b/assets/js/60d99771.8031c813.js similarity index 98% rename from assets/js/60d99771.af816a56.js rename to assets/js/60d99771.8031c813.js index 034226ebe2..4b7f2ae0d6 100644 --- a/assets/js/60d99771.af816a56.js +++ b/assets/js/60d99771.8031c813.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5356],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),u=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==u&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",b.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},10821:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>u,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const u={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-5.0/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-5.0/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/5.0/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/query-plan.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"version-5.0/docs",previous:{title:"Connecting security to your framework",permalink:"/docs/5.0/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/5.0/prefetch-method"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5356],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),u=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==u&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",b.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},10821:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>u,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const u={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-5.0/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-5.0/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/5.0/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/query-plan.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"version-5.0/docs",previous:{title:"Connecting security to your framework",permalink:"/docs/5.0/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/5.0/prefetch-method"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/610e7425.05ef450c.js b/assets/js/610e7425.235491b2.js similarity index 98% rename from assets/js/610e7425.05ef450c.js rename to assets/js/610e7425.235491b2.js index 377cb42680..5eb416455b 100644 --- a/assets/js/610e7425.05ef450c.js +++ b/assets/js/610e7425.235491b2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4279],{28754:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var t=a(58168),i=(a(96540),a(15680));a(67443);const r={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},l=void 0,s={unversionedId:"inheritance-interfaces",id:"version-6.1/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-6.1/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/6.1/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/inheritance-interfaces.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"docs",previous:{title:"Input types",permalink:"/docs/6.1/input-types"},next:{title:"Error handling",permalink:"/docs/6.1/error-handling"}},p={},c=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],o={toc:c},g="wrapper";function d(e){let{components:n,...a}=e;return(0,i.yg)(g,(0,t.A)({},o,a,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,i.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,i.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,i.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will translate in GraphQL schema as:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,i.yg)("p",null,"Please note that you do not need to put the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,i.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,i.yg)("p",null,"You don't have to explicitly put a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")),(0,i.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,i.yg)("p",null,"In the example above, because the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,i.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4279],{28754:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var t=a(58168),i=(a(96540),a(15680));a(67443);const r={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},l=void 0,s={unversionedId:"inheritance-interfaces",id:"version-6.1/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-6.1/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/6.1/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/inheritance-interfaces.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"docs",previous:{title:"Input types",permalink:"/docs/6.1/input-types"},next:{title:"Error handling",permalink:"/docs/6.1/error-handling"}},p={},c=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],o={toc:c},g="wrapper";function d(e){let{components:n,...a}=e;return(0,i.yg)(g,(0,t.A)({},o,a,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,i.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,i.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,i.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will translate in GraphQL schema as:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,i.yg)("p",null,"Please note that you do not need to put the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,i.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,i.yg)("p",null,"You don't have to explicitly put a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")),(0,i.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,i.yg)("p",null,"In the example above, because the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,i.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/612b773e.977652cc.js b/assets/js/612b773e.21049eaa.js similarity index 82% rename from assets/js/612b773e.977652cc.js rename to assets/js/612b773e.21049eaa.js index 002005e128..a0d6187c65 100644 --- a/assets/js/612b773e.977652cc.js +++ b/assets/js/612b773e.21049eaa.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5646],{51054:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>u,contentTitle:()=>s,default:()=>p,frontMatter:()=>o,metadata:()=>r,toc:()=>d});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,r={unversionedId:"mutations",id:"mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/docs/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/next/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/mutations.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"docs",previous:{title:"Queries",permalink:"/docs/next/queries"},next:{title:"Subscriptions",permalink:"/docs/next/subscriptions"}},u={},d=[],l={toc:d},c="wrapper";function p(t){let{components:e,...n}=t;return(0,i.yg)(c,(0,a.A)({},l,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In GraphQLite, mutations are created ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/queries"},"like queries"),"."),(0,i.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," attribute."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5646],{51054:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>u,contentTitle:()=>s,default:()=>p,frontMatter:()=>o,metadata:()=>r,toc:()=>l});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,r={unversionedId:"mutations",id:"mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/docs/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/next/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/mutations.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"docs",previous:{title:"Queries",permalink:"/docs/next/queries"},next:{title:"Subscriptions",permalink:"/docs/next/subscriptions"}},u={},l=[],d={toc:l},c="wrapper";function p(t){let{components:e,...n}=t;return(0,i.yg)(c,(0,a.A)({},d,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In GraphQLite, mutations are created ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/queries"},"like queries"),"."),(0,i.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," attribute."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/61595218.ac93c769.js b/assets/js/61595218.9e0c75e2.js similarity index 99% rename from assets/js/61595218.ac93c769.js rename to assets/js/61595218.9e0c75e2.js index 20c297a9ac..d25421d0d5 100644 --- a/assets/js/61595218.ac93c769.js +++ b/assets/js/61595218.9e0c75e2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9775],{78764:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>g});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,o={unversionedId:"annotations-reference",id:"version-4.2/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-4.2/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/4.2/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/annotations-reference.md",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"version-4.2/docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/4.2/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/4.2/semver"}},p={},g=[{value:"@Query annotation",id:"query-annotation",level:2},{value:"@Mutation annotation",id:"mutation-annotation",level:2},{value:"@Type annotation",id:"type-annotation",level:2},{value:"@ExtendType annotation",id:"extendtype-annotation",level:2},{value:"@Input annotation",id:"input-annotation",level:2},{value:"@Field annotation",id:"field-annotation",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"@Logged annotation",id:"logged-annotation",level:2},{value:"@Right annotation",id:"right-annotation",level:2},{value:"@FailWith annotation",id:"failwith-annotation",level:2},{value:"@HideIfUnauthorized annotation",id:"hideifunauthorized-annotation",level:2},{value:"@InjectUser annotation",id:"injectuser-annotation",level:2},{value:"@Security annotation",id:"security-annotation",level:2},{value:"@Factory annotation",id:"factory-annotation",level:2},{value:"@UseInputType annotation",id:"useinputtype-annotation",level:2},{value:"@Decorate annotation",id:"decorate-annotation",level:2},{value:"@Autowire annotation",id:"autowire-annotation",level:2},{value:"@HideParameter annotation",id:"hideparameter-annotation",level:2},{value:"@Validate annotation",id:"validate-annotation",level:2},{value:"@Assertion annotation",id:"assertion-annotation",level:2},{value:"@EnumType annotation",id:"enumtype-annotation",level:2}],y={toc:g},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},y,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query-annotation"},"@Query annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation-annotation"},"@Mutation annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type-annotation"},"@Type annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, ',(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/external-type-declaration"},"the class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype-annotation"},"@ExtendType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input-annotation"},"@Input annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true")," if name is not specified. Whether the annotated PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("em",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation.")))),(0,l.yg)("h2",{id:"field-annotation"},"@Field annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield-annotation"},"@SourceField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield-annotation"},"@MagicField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged-annotation"},"@Logged annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right-annotation"},"@Right annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith-annotation"},"@FailWith annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized-annotation"},"@HideIfUnauthorized annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser-annotation"},"@InjectUser annotation"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security-annotation"},"@Security annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory-annotation"},"@Factory annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype-annotation"},"@UseInputType annotation"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate-annotation"},"@Decorate annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire-annotation"},"@Autowire annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter-annotation"},"@HideParameter annotation"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate-annotation"},"@Validate annotation"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion-annotation"},"@Assertion annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype-annotation"},"@EnumType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9775],{78764:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>g});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,o={unversionedId:"annotations-reference",id:"version-4.2/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-4.2/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/4.2/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/annotations-reference.md",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"version-4.2/docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/4.2/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/4.2/semver"}},p={},g=[{value:"@Query annotation",id:"query-annotation",level:2},{value:"@Mutation annotation",id:"mutation-annotation",level:2},{value:"@Type annotation",id:"type-annotation",level:2},{value:"@ExtendType annotation",id:"extendtype-annotation",level:2},{value:"@Input annotation",id:"input-annotation",level:2},{value:"@Field annotation",id:"field-annotation",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"@Logged annotation",id:"logged-annotation",level:2},{value:"@Right annotation",id:"right-annotation",level:2},{value:"@FailWith annotation",id:"failwith-annotation",level:2},{value:"@HideIfUnauthorized annotation",id:"hideifunauthorized-annotation",level:2},{value:"@InjectUser annotation",id:"injectuser-annotation",level:2},{value:"@Security annotation",id:"security-annotation",level:2},{value:"@Factory annotation",id:"factory-annotation",level:2},{value:"@UseInputType annotation",id:"useinputtype-annotation",level:2},{value:"@Decorate annotation",id:"decorate-annotation",level:2},{value:"@Autowire annotation",id:"autowire-annotation",level:2},{value:"@HideParameter annotation",id:"hideparameter-annotation",level:2},{value:"@Validate annotation",id:"validate-annotation",level:2},{value:"@Assertion annotation",id:"assertion-annotation",level:2},{value:"@EnumType annotation",id:"enumtype-annotation",level:2}],y={toc:g},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},y,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query-annotation"},"@Query annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation-annotation"},"@Mutation annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type-annotation"},"@Type annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, ',(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/external-type-declaration"},"the class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype-annotation"},"@ExtendType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input-annotation"},"@Input annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true")," if name is not specified. Whether the annotated PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("em",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation.")))),(0,l.yg)("h2",{id:"field-annotation"},"@Field annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield-annotation"},"@SourceField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield-annotation"},"@MagicField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged-annotation"},"@Logged annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right-annotation"},"@Right annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith-annotation"},"@FailWith annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized-annotation"},"@HideIfUnauthorized annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser-annotation"},"@InjectUser annotation"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security-annotation"},"@Security annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory-annotation"},"@Factory annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.2/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype-annotation"},"@UseInputType annotation"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate-annotation"},"@Decorate annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire-annotation"},"@Autowire annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter-annotation"},"@HideParameter annotation"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate-annotation"},"@Validate annotation"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion-annotation"},"@Assertion annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.2/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype-annotation"},"@EnumType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/617523b3.8b55c2b2.js b/assets/js/617523b3.a43a650a.js similarity index 98% rename from assets/js/617523b3.8b55c2b2.js rename to assets/js/617523b3.a43a650a.js index 0fe93976b9..b2b5a4dbf1 100644 --- a/assets/js/617523b3.8b55c2b2.js +++ b/assets/js/617523b3.a43a650a.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6924],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),p=a(57485),o=a(31682),s=a(89466);function d(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function c(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,o.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,p.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=c(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[p,o]=m({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=p??d;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),o(e),h(e)}),[o,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:p,tabValues:o}=e;const s=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=s.indexOf(t),n=o[a].value;n!==i&&(d(t),p(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},o.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:c},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},64643:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>p,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>d});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types",original_id:"multiple_output_types"},p=void 0,o={unversionedId:"multiple_output_types",id:"version-4.1/multiple_output_types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.1/multiple_output_types.mdx",sourceDirName:".",slug:"/multiple_output_types",permalink:"/docs/4.1/multiple_output_types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/multiple_output_types.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types",original_id:"multiple_output_types"},sidebar:"version-4.1/docs",previous:{title:"Extending an input type",permalink:"/docs/4.1/extend_input_type"},next:{title:"Symfony specific features",permalink:"/docs/4.1/symfony-bundle-advanced"}},s={},d=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],c={toc:d},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/external_type_declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6924],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),p=a(57485),o=a(31682),s=a(89466);function d(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function c(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,o.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,p.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=c(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[p,o]=m({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=p??d;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),o(e),h(e)}),[o,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:p,tabValues:o}=e;const s=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=s.indexOf(t),n=o[a].value;n!==i&&(d(t),p(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},o.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:c},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},64643:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>p,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>d});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types",original_id:"multiple_output_types"},p=void 0,o={unversionedId:"multiple_output_types",id:"version-4.1/multiple_output_types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.1/multiple_output_types.mdx",sourceDirName:".",slug:"/multiple_output_types",permalink:"/docs/4.1/multiple_output_types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/multiple_output_types.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types",original_id:"multiple_output_types"},sidebar:"version-4.1/docs",previous:{title:"Extending an input type",permalink:"/docs/4.1/extend_input_type"},next:{title:"Symfony specific features",permalink:"/docs/4.1/symfony-bundle-advanced"}},s={},d=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],c={toc:d},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/external_type_declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/61c7d915.b1aa6299.js b/assets/js/61c7d915.04828008.js similarity index 96% rename from assets/js/61c7d915.b1aa6299.js rename to assets/js/61c7d915.04828008.js index f786005295..5a048300a3 100644 --- a/assets/js/61c7d915.b1aa6299.js +++ b/assets/js/61c7d915.04828008.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6621],{70267:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>l,default:()=>h,frontMatter:()=>o,metadata:()=>i,toc:()=>s});var a=t(58168),r=(t(96540),t(15680));t(67443);const o={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},l=void 0,i={unversionedId:"query-plan",id:"version-6.1/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-6.1/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/6.1/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/query-plan.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"docs",previous:{title:"Connecting security to your framework",permalink:"/docs/6.1/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/6.1/prefetch-method"}},u={},s=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],p={toc:s},d="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6621],{70267:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>l,default:()=>h,frontMatter:()=>o,metadata:()=>i,toc:()=>s});var a=t(58168),r=(t(96540),t(15680));t(67443);const o={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},l=void 0,i={unversionedId:"query-plan",id:"version-6.1/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-6.1/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/6.1/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/query-plan.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"docs",previous:{title:"Connecting security to your framework",permalink:"/docs/6.1/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/6.1/prefetch-method"}},u={},s=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],p={toc:s},d="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/623b6c78.0172bd69.js b/assets/js/623b6c78.2900a0a2.js similarity index 85% rename from assets/js/623b6c78.0172bd69.js rename to assets/js/623b6c78.2900a0a2.js index 85f318345c..66bf055675 100644 --- a/assets/js/623b6c78.0172bd69.js +++ b/assets/js/623b6c78.2900a0a2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7800],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),o=n(23104),l=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),b=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{b&&l(b)}),[b]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:l,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},o,{className:(0,s.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",y.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,b.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},27534:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),o=n(19365);const l={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records",original_id:"prefetch-method"},i=void 0,u={unversionedId:"prefetch-method",id:"version-4.1/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-4.1/prefetch_method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/4.1/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/prefetch_method.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records",original_id:"prefetch-method"},sidebar:"version-4.1/docs",previous:{title:"Query plan",permalink:"/docs/4.1/query-plan"},next:{title:"File uploads",permalink:"/docs/4.1/file-uploads"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))),(0,r.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7800],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),l=n(23104),o=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),b=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},l,{className:(0,s.A)("tabs__item",y.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",y.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,b.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},27534:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>o,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),l=n(19365);const o={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records",original_id:"prefetch-method"},i=void 0,u={unversionedId:"prefetch-method",id:"version-4.1/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-4.1/prefetch_method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/4.1/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/prefetch_method.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records",original_id:"prefetch-method"},sidebar:"version-4.1/docs",previous:{title:"Query plan",permalink:"/docs/4.1/query-plan"},next:{title:"File uploads",permalink:"/docs/4.1/file-uploads"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))),(0,r.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/64536e1a.3de73596.js b/assets/js/64536e1a.9ea966df.js similarity index 98% rename from assets/js/64536e1a.3de73596.js rename to assets/js/64536e1a.9ea966df.js index ddcb3d5dcd..cd730e9ef1 100644 --- a/assets/js/64536e1a.3de73596.js +++ b/assets/js/64536e1a.9ea966df.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2960],{10976:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>h,frontMatter:()=>o,metadata:()=>r,toc:()=>s});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"operation-complexity",title:"Operation complexity",sidebar_label:"Operation complexity"},l=void 0,r={unversionedId:"operation-complexity",id:"operation-complexity",title:"Operation complexity",description:"At some point you may find yourself receiving queries with an insane amount of requested",source:"@site/docs/operation-complexity.md",sourceDirName:".",slug:"/operation-complexity",permalink:"/docs/next/operation-complexity",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/operation-complexity.md",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"operation-complexity",title:"Operation complexity",sidebar_label:"Operation complexity"},sidebar:"docs",previous:{title:"Connecting security to your framework",permalink:"/docs/next/implementing-security"},next:{title:"Query plan",permalink:"/docs/next/query-plan"}},p={},s=[{value:"Query depth",id:"query-depth",level:2},{value:"Static request analysis",id:"static-request-analysis",level:2},{value:"Full request analysis",id:"full-request-analysis",level:2},{value:"Setup",id:"setup",level:2}],u={toc:s},y="wrapper";function h(e){let{components:t,...n}=e;return(0,i.yg)(y,(0,a.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"At some point you may find yourself receiving queries with an insane amount of requested\nfields or items, all at once. Usually, it's not a good thing, so you may want to somehow\nlimit the amount of requests or their individual complexity. "),(0,i.yg)("h2",{id:"query-depth"},"Query depth"),(0,i.yg)("p",null,"The simplest way to limit complexity is to limit the max query depth. ",(0,i.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php"),",\nwhich GraphQLite relies on, ",(0,i.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/security/#limiting-query-depth"},"has this built in"),".\nTo use it, you may use ",(0,i.yg)("inlineCode",{parentName:"p"},"addValidationRule")," when building your PSR15 middleware:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$builder->addValidationRule(new \\GraphQL\\Validator\\Rules\\QueryDepth(7));\n")),(0,i.yg)("p",null,"Although this works for simple cases, this doesn't prevent requesting an excessive amount\nof fields on the depth of under 7, nor does it prevent requesting too many nodes in paginated lists.\nThis is where automatic query complexity comes to save us."),(0,i.yg)("h2",{id:"static-request-analysis"},"Static request analysis"),(0,i.yg)("p",null,"The operation complexity analyzer is a useful tool to make your API secure. The operation\ncomplexity analyzer assigns by default every field a complexity of ",(0,i.yg)("inlineCode",{parentName:"p"},"1"),". The complexity of\nall fields in one of the operations of a GraphQL request is not allowed to be greater\nthan the maximum permitted operation complexity."),(0,i.yg)("p",null,"This sounds fairly simple at first, but the more you think about this, the more you\nwonder if that is so. Does every field have the same complexity?"),(0,i.yg)("p",null,"In a data graph, not every field is the same. We have fields that fetch data that are\nmore expensive than fields that just complete already resolved data."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"type Query {\n books(take: Int = 10): [Book]\n}\n\ntype Book {\n title\n author: Author\n}\n\ntype Author {\n name\n}\n")),(0,i.yg)("p",null,"In the above example executing the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field on the ",(0,i.yg)("inlineCode",{parentName:"p"},"Query")," type might go to the\ndatabase and fetch the ",(0,i.yg)("inlineCode",{parentName:"p"},"Book"),". This means that the cost of the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field is\nprobably higher than the cost of the ",(0,i.yg)("inlineCode",{parentName:"p"},"title")," field. The cost of the title field\nmight be the impact on the memory and to the transport. For ",(0,i.yg)("inlineCode",{parentName:"p"},"title"),", the default\ncost of ",(0,i.yg)("inlineCode",{parentName:"p"},"1")," os OK. But for ",(0,i.yg)("inlineCode",{parentName:"p"},"books"),", we might want to go with a higher cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"10"),"\nsince we are getting a list of books from our database."),(0,i.yg)("p",null,"Moreover, we have the field ",(0,i.yg)("inlineCode",{parentName:"p"},"author")," on the book, which might go to the database\nas well to fetch the ",(0,i.yg)("inlineCode",{parentName:"p"},"Author")," object. Since we are only fetching a single item here,\nwe might want to apply a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"5")," to this field."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class Controller {\n /**\n * @return Book[]\n */\n #[Query]\n #[Cost(complexity: 10)]\n public function books(int $take = 10): array {}\n}\n\n#[Type]\nclass Book {\n #[Field]\n public string $title;\n \n #[Field]\n #[Cost(complexity: 5)]\n public Author $author;\n}\n\n#[Type]\nclass Author {\n #[Field]\n public string $name;\n}\n")),(0,i.yg)("p",null,"If we run the following query against our data graph, we will come up with the cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"11"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n }\n}\n")),(0,i.yg)("p",null,"When drilling in further, a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"17")," occurs."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("p",null,"This kind of analysis is entirely static and could just be done by inspecting the\nquery syntax tree. The impact on the overall execution performance is very low.\nBut with this static approach, we do have a very rough idea of the performance.\nIs it correct to apply always a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"10")," even though we might get one or one\nhundred books back?"),(0,i.yg)("h2",{id:"full-request-analysis"},"Full request analysis"),(0,i.yg)("p",null,"The operation complexity analyzer can also take arguments into account when analyzing operation complexity."),(0,i.yg)("p",null,"If we look at our data graph, we can see that the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field actually has an argument\nthat defines how many books are returned. The ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," argument, in this case, specifies\nthe maximum books that the field will return."),(0,i.yg)("p",null,"When measuring the field","`","s impact, we can take the argument ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," into account as a\nmultiplier of our cost. This means we might want to lower the cost to ",(0,i.yg)("inlineCode",{parentName:"p"},"5")," since now we\nget a more fine-grained cost calculation by multiplying the complexity\nof the field with the ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," argument."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class Controller {\n /**\n * @return Book[]\n */\n #[Query]\n #[Cost(complexity: 5, multipliers: ['take'], defaultMultiplier: 200)]\n public function books(?int $take = 10): array {}\n}\n\n#[Type]\nclass Book {\n #[Field]\n public string $title;\n \n #[Field]\n #[Cost(complexity: 5)]\n public Author $author;\n}\n\n#[Type]\nclass Author {\n #[Field]\n public string $name;\n}\n")),(0,i.yg)("p",null,"With the multiplier in place, we now get a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"60")," for the request since the multiplier\nis applied to the books field and the child fields' cost. If multiple multipliers are specified,\nthe cost will be multiplied by each of the fields."),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"10 * (5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n }\n}\n")),(0,i.yg)("p",null,"When drilling in further, the cost will go up to ",(0,i.yg)("inlineCode",{parentName:"p"},"240")," since we are now pulling twice as much books and also their authors."),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"20 * (5 + 1 + 5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books(take: 20) {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("p",null,"Notice the nullable ",(0,i.yg)("inlineCode",{parentName:"p"},"$take")," parameter. This might come in handy if ",(0,i.yg)("inlineCode",{parentName:"p"},"take: null"),' means "get all items",\nbut that would also mean that the overall complexity would only be ',(0,i.yg)("inlineCode",{parentName:"p"},"1 + 5 + 1 + 5 + 1 = 11"),",\nwhen in fact that would be a very costly query to execute. "),(0,i.yg)("p",null,"If all of the multiplier fields are either ",(0,i.yg)("inlineCode",{parentName:"p"},"null")," or missing (and don't have default values),\n",(0,i.yg)("inlineCode",{parentName:"p"},"defaultMultiplier")," is used:"),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"200 * (5 + 1 + 5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books(take: null) {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("h2",{id:"setup"},"Setup"),(0,i.yg)("p",null,"As with query depth, automatic query complexity is configured through PSR15 middleware:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n// Total query cost cannot exceed 1000 points\n$builder->limitQueryComplexity(1000);\n")),(0,i.yg)("p",null,"Beware that introspection queries would also be limited in complexity. A full introspection\nquery sits at around ",(0,i.yg)("inlineCode",{parentName:"p"},"107")," points, so we recommend a minimum of ",(0,i.yg)("inlineCode",{parentName:"p"},"150")," for query complexity limit."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2960],{10976:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>h,frontMatter:()=>o,metadata:()=>r,toc:()=>s});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"operation-complexity",title:"Operation complexity",sidebar_label:"Operation complexity"},l=void 0,r={unversionedId:"operation-complexity",id:"operation-complexity",title:"Operation complexity",description:"At some point you may find yourself receiving queries with an insane amount of requested",source:"@site/docs/operation-complexity.md",sourceDirName:".",slug:"/operation-complexity",permalink:"/docs/next/operation-complexity",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/operation-complexity.md",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"operation-complexity",title:"Operation complexity",sidebar_label:"Operation complexity"},sidebar:"docs",previous:{title:"Connecting security to your framework",permalink:"/docs/next/implementing-security"},next:{title:"Query plan",permalink:"/docs/next/query-plan"}},p={},s=[{value:"Query depth",id:"query-depth",level:2},{value:"Static request analysis",id:"static-request-analysis",level:2},{value:"Full request analysis",id:"full-request-analysis",level:2},{value:"Setup",id:"setup",level:2}],u={toc:s},y="wrapper";function h(e){let{components:t,...n}=e;return(0,i.yg)(y,(0,a.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"At some point you may find yourself receiving queries with an insane amount of requested\nfields or items, all at once. Usually, it's not a good thing, so you may want to somehow\nlimit the amount of requests or their individual complexity. "),(0,i.yg)("h2",{id:"query-depth"},"Query depth"),(0,i.yg)("p",null,"The simplest way to limit complexity is to limit the max query depth. ",(0,i.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php"),",\nwhich GraphQLite relies on, ",(0,i.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/security/#limiting-query-depth"},"has this built in"),".\nTo use it, you may use ",(0,i.yg)("inlineCode",{parentName:"p"},"addValidationRule")," when building your PSR15 middleware:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$builder->addValidationRule(new \\GraphQL\\Validator\\Rules\\QueryDepth(7));\n")),(0,i.yg)("p",null,"Although this works for simple cases, this doesn't prevent requesting an excessive amount\nof fields on the depth of under 7, nor does it prevent requesting too many nodes in paginated lists.\nThis is where automatic query complexity comes to save us."),(0,i.yg)("h2",{id:"static-request-analysis"},"Static request analysis"),(0,i.yg)("p",null,"The operation complexity analyzer is a useful tool to make your API secure. The operation\ncomplexity analyzer assigns by default every field a complexity of ",(0,i.yg)("inlineCode",{parentName:"p"},"1"),". The complexity of\nall fields in one of the operations of a GraphQL request is not allowed to be greater\nthan the maximum permitted operation complexity."),(0,i.yg)("p",null,"This sounds fairly simple at first, but the more you think about this, the more you\nwonder if that is so. Does every field have the same complexity?"),(0,i.yg)("p",null,"In a data graph, not every field is the same. We have fields that fetch data that are\nmore expensive than fields that just complete already resolved data."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"type Query {\n books(take: Int = 10): [Book]\n}\n\ntype Book {\n title\n author: Author\n}\n\ntype Author {\n name\n}\n")),(0,i.yg)("p",null,"In the above example executing the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field on the ",(0,i.yg)("inlineCode",{parentName:"p"},"Query")," type might go to the\ndatabase and fetch the ",(0,i.yg)("inlineCode",{parentName:"p"},"Book"),". This means that the cost of the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field is\nprobably higher than the cost of the ",(0,i.yg)("inlineCode",{parentName:"p"},"title")," field. The cost of the title field\nmight be the impact on the memory and to the transport. For ",(0,i.yg)("inlineCode",{parentName:"p"},"title"),", the default\ncost of ",(0,i.yg)("inlineCode",{parentName:"p"},"1")," os OK. But for ",(0,i.yg)("inlineCode",{parentName:"p"},"books"),", we might want to go with a higher cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"10"),"\nsince we are getting a list of books from our database."),(0,i.yg)("p",null,"Moreover, we have the field ",(0,i.yg)("inlineCode",{parentName:"p"},"author")," on the book, which might go to the database\nas well to fetch the ",(0,i.yg)("inlineCode",{parentName:"p"},"Author")," object. Since we are only fetching a single item here,\nwe might want to apply a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"5")," to this field."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class Controller {\n /**\n * @return Book[]\n */\n #[Query]\n #[Cost(complexity: 10)]\n public function books(int $take = 10): array {}\n}\n\n#[Type]\nclass Book {\n #[Field]\n public string $title;\n \n #[Field]\n #[Cost(complexity: 5)]\n public Author $author;\n}\n\n#[Type]\nclass Author {\n #[Field]\n public string $name;\n}\n")),(0,i.yg)("p",null,"If we run the following query against our data graph, we will come up with the cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"11"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n }\n}\n")),(0,i.yg)("p",null,"When drilling in further, a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"17")," occurs."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("p",null,"This kind of analysis is entirely static and could just be done by inspecting the\nquery syntax tree. The impact on the overall execution performance is very low.\nBut with this static approach, we do have a very rough idea of the performance.\nIs it correct to apply always a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"10")," even though we might get one or one\nhundred books back?"),(0,i.yg)("h2",{id:"full-request-analysis"},"Full request analysis"),(0,i.yg)("p",null,"The operation complexity analyzer can also take arguments into account when analyzing operation complexity."),(0,i.yg)("p",null,"If we look at our data graph, we can see that the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field actually has an argument\nthat defines how many books are returned. The ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," argument, in this case, specifies\nthe maximum books that the field will return."),(0,i.yg)("p",null,"When measuring the field","`","s impact, we can take the argument ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," into account as a\nmultiplier of our cost. This means we might want to lower the cost to ",(0,i.yg)("inlineCode",{parentName:"p"},"5")," since now we\nget a more fine-grained cost calculation by multiplying the complexity\nof the field with the ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," argument."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class Controller {\n /**\n * @return Book[]\n */\n #[Query]\n #[Cost(complexity: 5, multipliers: ['take'], defaultMultiplier: 200)]\n public function books(?int $take = 10): array {}\n}\n\n#[Type]\nclass Book {\n #[Field]\n public string $title;\n \n #[Field]\n #[Cost(complexity: 5)]\n public Author $author;\n}\n\n#[Type]\nclass Author {\n #[Field]\n public string $name;\n}\n")),(0,i.yg)("p",null,"With the multiplier in place, we now get a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"60")," for the request since the multiplier\nis applied to the books field and the child fields' cost. If multiple multipliers are specified,\nthe cost will be multiplied by each of the fields."),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"10 * (5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n }\n}\n")),(0,i.yg)("p",null,"When drilling in further, the cost will go up to ",(0,i.yg)("inlineCode",{parentName:"p"},"240")," since we are now pulling twice as much books and also their authors."),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"20 * (5 + 1 + 5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books(take: 20) {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("p",null,"Notice the nullable ",(0,i.yg)("inlineCode",{parentName:"p"},"$take")," parameter. This might come in handy if ",(0,i.yg)("inlineCode",{parentName:"p"},"take: null"),' means "get all items",\nbut that would also mean that the overall complexity would only be ',(0,i.yg)("inlineCode",{parentName:"p"},"1 + 5 + 1 + 5 + 1 = 11"),",\nwhen in fact that would be a very costly query to execute. "),(0,i.yg)("p",null,"If all of the multiplier fields are either ",(0,i.yg)("inlineCode",{parentName:"p"},"null")," or missing (and don't have default values),\n",(0,i.yg)("inlineCode",{parentName:"p"},"defaultMultiplier")," is used:"),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"200 * (5 + 1 + 5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books(take: null) {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("h2",{id:"setup"},"Setup"),(0,i.yg)("p",null,"As with query depth, automatic query complexity is configured through PSR15 middleware:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n// Total query cost cannot exceed 1000 points\n$builder->limitQueryComplexity(1000);\n")),(0,i.yg)("p",null,"Beware that introspection queries would also be limited in complexity. A full introspection\nquery sits at around ",(0,i.yg)("inlineCode",{parentName:"p"},"107")," points, so we recommend a minimum of ",(0,i.yg)("inlineCode",{parentName:"p"},"150")," for query complexity limit."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/64947e00.fc42f77e.js b/assets/js/64947e00.15f950f3.js similarity index 99% rename from assets/js/64947e00.fc42f77e.js rename to assets/js/64947e00.15f950f3.js index 9c71966f89..2be655cdec 100644 --- a/assets/js/64947e00.fc42f77e.js +++ b/assets/js/64947e00.15f950f3.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8722],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),l=n(20053);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),l=n(96540),r=n(20053),i=n(23104),o=n(56347),u=n(57485),p=n(31682),s=n(89466);function c(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:l}}=e;return{value:t,label:n,attributes:a,default:l}}))}function d(e){const{values:t,children:n}=e;return(0,l.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=d(e),[i,o]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[u,p]=g({queryString:n,groupId:a}),[c,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,s.Dv)(n);return[a,(0,l.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),h=(()=>{const e=u??c;return y({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:i,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),p(e),m(e)}),[p,m,r]),tabValues:r}}var h=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:u,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=s.indexOf(t),a=p[n].value;a!==o&&(c(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=s.indexOf(e.currentTarget)+1;t=s[n]??s[0];break}case"ArrowLeft":{const n=s.indexOf(e.currentTarget)-1;t=s[n]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:i}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:d},i,{className:(0,r.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function I(e){const t=m(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,t)),l.createElement(v,(0,a.A)({},e,t)))}function T(e){const t=(0,h.A)();return l.createElement(I,(0,a.A)({key:String(t)},e))}},27373:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>u,default:()=>g,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var a=n(58168),l=(n(96540),n(15680)),r=(n(67443),n(11470)),i=n(19365);const o={id:"input-types",title:"Input types",sidebar_label:"Input types"},u=void 0,p={unversionedId:"input-types",id:"version-5.0/input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-5.0/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/5.0/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/input-types.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"version-5.0/docs",previous:{title:"External type declaration",permalink:"/docs/5.0/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/5.0/inheritance-interfaces"}},s={},c=[{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3},{value:"@Input Annotation",id:"input-annotation",level:2},{value:"Multiple input types per one class",id:"multiple-input-types-per-one-class",level:3}],d={toc:c},y="wrapper";function g(e){let{components:t,...n}=e;return(0,l.yg)(y,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,l.yg)("p",null,"Your GraphQL query might look like this:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,l.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,l.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,l.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,l.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using ",(0,l.yg)("strong",{parentName:"p"},"Factory")," or annotating the class with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),"."),(0,l.yg)("h2",{id:"factory"},"Factory"),(0,l.yg)("p",null,"A ",(0,l.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,l.yg)("p",null,"Here is an example of factory:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")))),(0,l.yg)("p",null,"and now, you can run query like this:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,l.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,l.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,l.yg)("p",null,"A few important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,l.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,l.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,l.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,l.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")))),(0,l.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')))),(0,l.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,l.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,l.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,l.yg)("p",null,"You can use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,l.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')))),(0,l.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,l.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,l.yg)("p",null,"Here is an annotated sample:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')))),(0,l.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,l.yg)("p",null,"Image your ",(0,l.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,l.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')))),(0,l.yg)("p",null,"With the ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,l.yg)("p",null,"To be able to hide an argument, the argument must have a default value."),(0,l.yg)("h2",{id:"input-annotation"},"@Input Annotation"),(0,l.yg)("p",null,"Let's transform ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," class into an input type by adding ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation to it and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to corresponding properties:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Input\n */\nclass Location\n{\n\n /**\n * @Field\n * @var float\n */\n private $latitude;\n\n /**\n * @Field\n * @var float\n */\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"Now if you call ",(0,l.yg)("inlineCode",{parentName:"p"},"getCities()")," query you can pass the location input in the same way as with factories.\nThe ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with provided ",(0,l.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,l.yg)("inlineCode",{parentName:"p"},"longitude")," and passed to the controller as a parameter."),(0,l.yg)("p",null,"There are some important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"@Field")," annotation is recognized only on properties for Input Type."),(0,l.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,l.yg)("ul",{parentName:"li"},(0,l.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,l.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort."),(0,l.yg)("li",{parentName:"ul"},"For private or protected properties implemented public setter is required (if they are not set via constructor). For example ",(0,l.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),".")))),(0,l.yg)("h3",{id:"multiple-input-types-per-one-class"},"Multiple input types per one class"),(0,l.yg)("p",null,"Simple usage of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),' annotation on a class creates an GraphQl input named by class name + "Input" suffix if a class name does not end with it already.\nYou can add multiple ',(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotations to the same class, give them different names and link different fields.\nConsider the following example:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n #[Field]\n public ?int $age;\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Input(name="CreateUserInput", default=true)\n * @Input(name="UpdateUserInput", update=true)\n */\nclass UserInput\n{\n\n /**\n * @Field()\n * @var string\n */\n public $username;\n\n /**\n * @Field(for="CreateUserInput")\n * @var string\n */\n public string $email;\n\n /**\n * @Field(for="CreateUserInput", inputType="String!")\n * @Field(for="UpdateUserInput", inputType="String")\n * @var string|null\n */\n public $password;\n\n /**\n * @Field()\n * @var int|null\n */\n public $age;\n}\n')))),(0,l.yg)("p",null,"There are 2 input types created for just one class: ",(0,l.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,l.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,l.yg)("p",null,"Note that ",(0,l.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,l.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 7 they will be set to ",(0,l.yg)("inlineCode",{parentName:"p"},"null"),", while in PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8722],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),l=n(20053);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),l=n(96540),r=n(20053),i=n(23104),o=n(56347),u=n(57485),p=n(31682),s=n(89466);function c(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:l}}=e;return{value:t,label:n,attributes:a,default:l}}))}function d(e){const{values:t,children:n}=e;return(0,l.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),r=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=d(e),[i,o]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:r}))),[u,p]=g({queryString:n,groupId:a}),[c,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,s.Dv)(n);return[a,(0,l.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),h=(()=>{const e=u??c;return y({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:i,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),p(e),m(e)}),[p,m,r]),tabValues:r}}var h=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:u,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=s.indexOf(t),a=p[n].value;a!==o&&(c(t),u(a))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=s.indexOf(e.currentTarget)+1;t=s[n]??s[0];break}case"ArrowLeft":{const n=s.indexOf(e.currentTarget)-1;t=s[n]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:i}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:d},i,{className:(0,r.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const r=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function I(e){const t=m(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,t)),l.createElement(v,(0,a.A)({},e,t)))}function T(e){const t=(0,h.A)();return l.createElement(I,(0,a.A)({key:String(t)},e))}},27373:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>u,default:()=>g,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var a=n(58168),l=(n(96540),n(15680)),r=(n(67443),n(11470)),i=n(19365);const o={id:"input-types",title:"Input types",sidebar_label:"Input types"},u=void 0,p={unversionedId:"input-types",id:"version-5.0/input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-5.0/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/5.0/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/input-types.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"version-5.0/docs",previous:{title:"External type declaration",permalink:"/docs/5.0/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/5.0/inheritance-interfaces"}},s={},c=[{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3},{value:"@Input Annotation",id:"input-annotation",level:2},{value:"Multiple input types per one class",id:"multiple-input-types-per-one-class",level:3}],d={toc:c},y="wrapper";function g(e){let{components:t,...n}=e;return(0,l.yg)(y,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,l.yg)("p",null,"Your GraphQL query might look like this:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,l.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,l.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,l.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,l.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using ",(0,l.yg)("strong",{parentName:"p"},"Factory")," or annotating the class with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),"."),(0,l.yg)("h2",{id:"factory"},"Factory"),(0,l.yg)("p",null,"A ",(0,l.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,l.yg)("p",null,"Here is an example of factory:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")))),(0,l.yg)("p",null,"and now, you can run query like this:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,l.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,l.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,l.yg)("p",null,"A few important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,l.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,l.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,l.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,l.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")))),(0,l.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')))),(0,l.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,l.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,l.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,l.yg)("p",null,"You can use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,l.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')))),(0,l.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,l.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,l.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,l.yg)("p",null,"Here is an annotated sample:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n'))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')))),(0,l.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,l.yg)("p",null,"Image your ",(0,l.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,l.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')))),(0,l.yg)("p",null,"With the ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,l.yg)("p",null,"To be able to hide an argument, the argument must have a default value."),(0,l.yg)("h2",{id:"input-annotation"},"@Input Annotation"),(0,l.yg)("p",null,"Let's transform ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," class into an input type by adding ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation to it and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to corresponding properties:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Input\n */\nclass Location\n{\n\n /**\n * @Field\n * @var float\n */\n private $latitude;\n\n /**\n * @Field\n * @var float\n */\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")))),(0,l.yg)("p",null,"Now if you call ",(0,l.yg)("inlineCode",{parentName:"p"},"getCities()")," query you can pass the location input in the same way as with factories.\nThe ",(0,l.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with provided ",(0,l.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,l.yg)("inlineCode",{parentName:"p"},"longitude")," and passed to the controller as a parameter."),(0,l.yg)("p",null,"There are some important things to notice:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"@Field")," annotation is recognized only on properties for Input Type."),(0,l.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,l.yg)("ul",{parentName:"li"},(0,l.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,l.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort."),(0,l.yg)("li",{parentName:"ul"},"For private or protected properties implemented public setter is required (if they are not set via constructor). For example ",(0,l.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),".")))),(0,l.yg)("h3",{id:"multiple-input-types-per-one-class"},"Multiple input types per one class"),(0,l.yg)("p",null,"Simple usage of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),' annotation on a class creates an GraphQl input named by class name + "Input" suffix if a class name does not end with it already.\nYou can add multiple ',(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotations to the same class, give them different names and link different fields.\nConsider the following example:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n #[Field]\n public ?int $age;\n}\n"))),(0,l.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Input(name="CreateUserInput", default=true)\n * @Input(name="UpdateUserInput", update=true)\n */\nclass UserInput\n{\n\n /**\n * @Field()\n * @var string\n */\n public $username;\n\n /**\n * @Field(for="CreateUserInput")\n * @var string\n */\n public string $email;\n\n /**\n * @Field(for="CreateUserInput", inputType="String!")\n * @Field(for="UpdateUserInput", inputType="String")\n * @var string|null\n */\n public $password;\n\n /**\n * @Field()\n * @var int|null\n */\n public $age;\n}\n')))),(0,l.yg)("p",null,"There are 2 input types created for just one class: ",(0,l.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,l.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,l.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,l.yg)("li",{parentName:"ul"},"Field ",(0,l.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,l.yg)("p",null,"Note that ",(0,l.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,l.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,l.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 7 they will be set to ",(0,l.yg)("inlineCode",{parentName:"p"},"null"),", while in PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/652c74f1.ce075dcf.js b/assets/js/652c74f1.730aed76.js similarity index 99% rename from assets/js/652c74f1.ce075dcf.js rename to assets/js/652c74f1.730aed76.js index af5004f273..fcd1209f14 100644 --- a/assets/js/652c74f1.ce075dcf.js +++ b/assets/js/652c74f1.730aed76.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[211],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),i=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},21807:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const i={id:"queries",title:"Queries",sidebar_label:"Queries"},s=void 0,u={unversionedId:"queries",id:"version-4.3/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-4.3/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/4.3/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/queries.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"version-4.3/docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/4.3/other-frameworks"},next:{title:"Mutations",permalink:"/docs/4.3/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...i}=e;return(0,r.yg)(h,(0,a.A)({},d,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[211],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),i=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},21807:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const i={id:"queries",title:"Queries",sidebar_label:"Queries"},s=void 0,u={unversionedId:"queries",id:"version-4.3/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-4.3/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/4.3/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/queries.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"version-4.3/docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/4.3/other-frameworks"},next:{title:"Mutations",permalink:"/docs/4.3/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...i}=e;return(0,r.yg)(h,(0,a.A)({},d,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file diff --git a/assets/js/65b8d1d1.0dfc869c.js b/assets/js/65b8d1d1.047e76fa.js similarity index 98% rename from assets/js/65b8d1d1.0dfc869c.js rename to assets/js/65b8d1d1.047e76fa.js index 84fba211b1..29e295048b 100644 --- a/assets/js/65b8d1d1.0dfc869c.js +++ b/assets/js/65b8d1d1.047e76fa.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4330],{6299:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>h,frontMatter:()=>o,metadata:()=>r,toc:()=>s});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"operation-complexity",title:"Operation complexity",sidebar_label:"Operation complexity"},l=void 0,r={unversionedId:"operation-complexity",id:"version-7.0.0/operation-complexity",title:"Operation complexity",description:"At some point you may find yourself receiving queries with an insane amount of requested",source:"@site/versioned_docs/version-7.0.0/operation-complexity.md",sourceDirName:".",slug:"/operation-complexity",permalink:"/docs/operation-complexity",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/operation-complexity.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"operation-complexity",title:"Operation complexity",sidebar_label:"Operation complexity"},sidebar:"docs",previous:{title:"Connecting security to your framework",permalink:"/docs/implementing-security"},next:{title:"Query plan",permalink:"/docs/query-plan"}},p={},s=[{value:"Query depth",id:"query-depth",level:2},{value:"Static request analysis",id:"static-request-analysis",level:2},{value:"Full request analysis",id:"full-request-analysis",level:2},{value:"Setup",id:"setup",level:2}],u={toc:s},y="wrapper";function h(e){let{components:t,...n}=e;return(0,i.yg)(y,(0,a.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"At some point you may find yourself receiving queries with an insane amount of requested\nfields or items, all at once. Usually, it's not a good thing, so you may want to somehow\nlimit the amount of requests or their individual complexity. "),(0,i.yg)("h2",{id:"query-depth"},"Query depth"),(0,i.yg)("p",null,"The simplest way to limit complexity is to limit the max query depth. ",(0,i.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php"),",\nwhich GraphQLite relies on, ",(0,i.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/security/#limiting-query-depth"},"has this built in"),".\nTo use it, you may use ",(0,i.yg)("inlineCode",{parentName:"p"},"addValidationRule")," when building your PSR15 middleware:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$builder->addValidationRule(new \\GraphQL\\Validator\\Rules\\QueryDepth(7));\n")),(0,i.yg)("p",null,"Although this works for simple cases, this doesn't prevent requesting an excessive amount\nof fields on the depth of under 7, nor does it prevent requesting too many nodes in paginated lists.\nThis is where automatic query complexity comes to save us."),(0,i.yg)("h2",{id:"static-request-analysis"},"Static request analysis"),(0,i.yg)("p",null,"The operation complexity analyzer is a useful tool to make your API secure. The operation\ncomplexity analyzer assigns by default every field a complexity of ",(0,i.yg)("inlineCode",{parentName:"p"},"1"),". The complexity of\nall fields in one of the operations of a GraphQL request is not allowed to be greater\nthan the maximum permitted operation complexity."),(0,i.yg)("p",null,"This sounds fairly simple at first, but the more you think about this, the more you\nwonder if that is so. Does every field have the same complexity?"),(0,i.yg)("p",null,"In a data graph, not every field is the same. We have fields that fetch data that are\nmore expensive than fields that just complete already resolved data."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"type Query {\n books(take: Int = 10): [Book]\n}\n\ntype Book {\n title\n author: Author\n}\n\ntype Author {\n name\n}\n")),(0,i.yg)("p",null,"In the above example executing the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field on the ",(0,i.yg)("inlineCode",{parentName:"p"},"Query")," type might go to the\ndatabase and fetch the ",(0,i.yg)("inlineCode",{parentName:"p"},"Book"),". This means that the cost of the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field is\nprobably higher than the cost of the ",(0,i.yg)("inlineCode",{parentName:"p"},"title")," field. The cost of the title field\nmight be the impact on the memory and to the transport. For ",(0,i.yg)("inlineCode",{parentName:"p"},"title"),", the default\ncost of ",(0,i.yg)("inlineCode",{parentName:"p"},"1")," os OK. But for ",(0,i.yg)("inlineCode",{parentName:"p"},"books"),", we might want to go with a higher cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"10"),"\nsince we are getting a list of books from our database."),(0,i.yg)("p",null,"Moreover, we have the field ",(0,i.yg)("inlineCode",{parentName:"p"},"author")," on the book, which might go to the database\nas well to fetch the ",(0,i.yg)("inlineCode",{parentName:"p"},"Author")," object. Since we are only fetching a single item here,\nwe might want to apply a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"5")," to this field."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class Controller {\n /**\n * @return Book[]\n */\n #[Query]\n #[Cost(complexity: 10)]\n public function books(int $take = 10): array {}\n}\n\n#[Type]\nclass Book {\n #[Field]\n public string $title;\n \n #[Field]\n #[Cost(complexity: 5)]\n public Author $author;\n}\n\n#[Type]\nclass Author {\n #[Field]\n public string $name;\n}\n")),(0,i.yg)("p",null,"If we run the following query against our data graph, we will come up with the cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"11"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n }\n}\n")),(0,i.yg)("p",null,"When drilling in further, a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"17")," occurs."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("p",null,"This kind of analysis is entirely static and could just be done by inspecting the\nquery syntax tree. The impact on the overall execution performance is very low.\nBut with this static approach, we do have a very rough idea of the performance.\nIs it correct to apply always a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"10")," even though we might get one or one\nhundred books back?"),(0,i.yg)("h2",{id:"full-request-analysis"},"Full request analysis"),(0,i.yg)("p",null,"The operation complexity analyzer can also take arguments into account when analyzing operation complexity."),(0,i.yg)("p",null,"If we look at our data graph, we can see that the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field actually has an argument\nthat defines how many books are returned. The ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," argument, in this case, specifies\nthe maximum books that the field will return."),(0,i.yg)("p",null,"When measuring the field","`","s impact, we can take the argument ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," into account as a\nmultiplier of our cost. This means we might want to lower the cost to ",(0,i.yg)("inlineCode",{parentName:"p"},"5")," since now we\nget a more fine-grained cost calculation by multiplying the complexity\nof the field with the ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," argument."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class Controller {\n /**\n * @return Book[]\n */\n #[Query]\n #[Cost(complexity: 5, multipliers: ['take'], defaultMultiplier: 200)]\n public function books(?int $take = 10): array {}\n}\n\n#[Type]\nclass Book {\n #[Field]\n public string $title;\n \n #[Field]\n #[Cost(complexity: 5)]\n public Author $author;\n}\n\n#[Type]\nclass Author {\n #[Field]\n public string $name;\n}\n")),(0,i.yg)("p",null,"With the multiplier in place, we now get a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"60")," for the request since the multiplier\nis applied to the books field and the child fields' cost. If multiple multipliers are specified,\nthe cost will be multiplied by each of the fields."),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"10 * (5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n }\n}\n")),(0,i.yg)("p",null,"When drilling in further, the cost will go up to ",(0,i.yg)("inlineCode",{parentName:"p"},"240")," since we are now pulling twice as much books and also their authors."),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"20 * (5 + 1 + 5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books(take: 20) {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("p",null,"Notice the nullable ",(0,i.yg)("inlineCode",{parentName:"p"},"$take")," parameter. This might come in handy if ",(0,i.yg)("inlineCode",{parentName:"p"},"take: null"),' means "get all items",\nbut that would also mean that the overall complexity would only be ',(0,i.yg)("inlineCode",{parentName:"p"},"1 + 5 + 1 + 5 + 1 = 11"),",\nwhen in fact that would be a very costly query to execute. "),(0,i.yg)("p",null,"If all of the multiplier fields are either ",(0,i.yg)("inlineCode",{parentName:"p"},"null")," or missing (and don't have default values),\n",(0,i.yg)("inlineCode",{parentName:"p"},"defaultMultiplier")," is used:"),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"200 * (5 + 1 + 5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books(take: null) {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("h2",{id:"setup"},"Setup"),(0,i.yg)("p",null,"As with query depth, automatic query complexity is configured through PSR15 middleware:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n// Total query cost cannot exceed 1000 points\n$builder->limitQueryComplexity(1000);\n")),(0,i.yg)("p",null,"Beware that introspection queries would also be limited in complexity. A full introspection\nquery sits at around ",(0,i.yg)("inlineCode",{parentName:"p"},"107")," points, so we recommend a minimum of ",(0,i.yg)("inlineCode",{parentName:"p"},"150")," for query complexity limit."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4330],{6299:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>h,frontMatter:()=>o,metadata:()=>r,toc:()=>s});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"operation-complexity",title:"Operation complexity",sidebar_label:"Operation complexity"},l=void 0,r={unversionedId:"operation-complexity",id:"version-7.0.0/operation-complexity",title:"Operation complexity",description:"At some point you may find yourself receiving queries with an insane amount of requested",source:"@site/versioned_docs/version-7.0.0/operation-complexity.md",sourceDirName:".",slug:"/operation-complexity",permalink:"/docs/operation-complexity",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/operation-complexity.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"operation-complexity",title:"Operation complexity",sidebar_label:"Operation complexity"},sidebar:"docs",previous:{title:"Connecting security to your framework",permalink:"/docs/implementing-security"},next:{title:"Query plan",permalink:"/docs/query-plan"}},p={},s=[{value:"Query depth",id:"query-depth",level:2},{value:"Static request analysis",id:"static-request-analysis",level:2},{value:"Full request analysis",id:"full-request-analysis",level:2},{value:"Setup",id:"setup",level:2}],u={toc:s},y="wrapper";function h(e){let{components:t,...n}=e;return(0,i.yg)(y,(0,a.A)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"At some point you may find yourself receiving queries with an insane amount of requested\nfields or items, all at once. Usually, it's not a good thing, so you may want to somehow\nlimit the amount of requests or their individual complexity. "),(0,i.yg)("h2",{id:"query-depth"},"Query depth"),(0,i.yg)("p",null,"The simplest way to limit complexity is to limit the max query depth. ",(0,i.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php"),",\nwhich GraphQLite relies on, ",(0,i.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/security/#limiting-query-depth"},"has this built in"),".\nTo use it, you may use ",(0,i.yg)("inlineCode",{parentName:"p"},"addValidationRule")," when building your PSR15 middleware:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$builder->addValidationRule(new \\GraphQL\\Validator\\Rules\\QueryDepth(7));\n")),(0,i.yg)("p",null,"Although this works for simple cases, this doesn't prevent requesting an excessive amount\nof fields on the depth of under 7, nor does it prevent requesting too many nodes in paginated lists.\nThis is where automatic query complexity comes to save us."),(0,i.yg)("h2",{id:"static-request-analysis"},"Static request analysis"),(0,i.yg)("p",null,"The operation complexity analyzer is a useful tool to make your API secure. The operation\ncomplexity analyzer assigns by default every field a complexity of ",(0,i.yg)("inlineCode",{parentName:"p"},"1"),". The complexity of\nall fields in one of the operations of a GraphQL request is not allowed to be greater\nthan the maximum permitted operation complexity."),(0,i.yg)("p",null,"This sounds fairly simple at first, but the more you think about this, the more you\nwonder if that is so. Does every field have the same complexity?"),(0,i.yg)("p",null,"In a data graph, not every field is the same. We have fields that fetch data that are\nmore expensive than fields that just complete already resolved data."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"type Query {\n books(take: Int = 10): [Book]\n}\n\ntype Book {\n title\n author: Author\n}\n\ntype Author {\n name\n}\n")),(0,i.yg)("p",null,"In the above example executing the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field on the ",(0,i.yg)("inlineCode",{parentName:"p"},"Query")," type might go to the\ndatabase and fetch the ",(0,i.yg)("inlineCode",{parentName:"p"},"Book"),". This means that the cost of the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field is\nprobably higher than the cost of the ",(0,i.yg)("inlineCode",{parentName:"p"},"title")," field. The cost of the title field\nmight be the impact on the memory and to the transport. For ",(0,i.yg)("inlineCode",{parentName:"p"},"title"),", the default\ncost of ",(0,i.yg)("inlineCode",{parentName:"p"},"1")," os OK. But for ",(0,i.yg)("inlineCode",{parentName:"p"},"books"),", we might want to go with a higher cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"10"),"\nsince we are getting a list of books from our database."),(0,i.yg)("p",null,"Moreover, we have the field ",(0,i.yg)("inlineCode",{parentName:"p"},"author")," on the book, which might go to the database\nas well to fetch the ",(0,i.yg)("inlineCode",{parentName:"p"},"Author")," object. Since we are only fetching a single item here,\nwe might want to apply a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"5")," to this field."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class Controller {\n /**\n * @return Book[]\n */\n #[Query]\n #[Cost(complexity: 10)]\n public function books(int $take = 10): array {}\n}\n\n#[Type]\nclass Book {\n #[Field]\n public string $title;\n \n #[Field]\n #[Cost(complexity: 5)]\n public Author $author;\n}\n\n#[Type]\nclass Author {\n #[Field]\n public string $name;\n}\n")),(0,i.yg)("p",null,"If we run the following query against our data graph, we will come up with the cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"11"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n }\n}\n")),(0,i.yg)("p",null,"When drilling in further, a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"17")," occurs."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("p",null,"This kind of analysis is entirely static and could just be done by inspecting the\nquery syntax tree. The impact on the overall execution performance is very low.\nBut with this static approach, we do have a very rough idea of the performance.\nIs it correct to apply always a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"10")," even though we might get one or one\nhundred books back?"),(0,i.yg)("h2",{id:"full-request-analysis"},"Full request analysis"),(0,i.yg)("p",null,"The operation complexity analyzer can also take arguments into account when analyzing operation complexity."),(0,i.yg)("p",null,"If we look at our data graph, we can see that the ",(0,i.yg)("inlineCode",{parentName:"p"},"books")," field actually has an argument\nthat defines how many books are returned. The ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," argument, in this case, specifies\nthe maximum books that the field will return."),(0,i.yg)("p",null,"When measuring the field","`","s impact, we can take the argument ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," into account as a\nmultiplier of our cost. This means we might want to lower the cost to ",(0,i.yg)("inlineCode",{parentName:"p"},"5")," since now we\nget a more fine-grained cost calculation by multiplying the complexity\nof the field with the ",(0,i.yg)("inlineCode",{parentName:"p"},"take")," argument."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class Controller {\n /**\n * @return Book[]\n */\n #[Query]\n #[Cost(complexity: 5, multipliers: ['take'], defaultMultiplier: 200)]\n public function books(?int $take = 10): array {}\n}\n\n#[Type]\nclass Book {\n #[Field]\n public string $title;\n \n #[Field]\n #[Cost(complexity: 5)]\n public Author $author;\n}\n\n#[Type]\nclass Author {\n #[Field]\n public string $name;\n}\n")),(0,i.yg)("p",null,"With the multiplier in place, we now get a cost of ",(0,i.yg)("inlineCode",{parentName:"p"},"60")," for the request since the multiplier\nis applied to the books field and the child fields' cost. If multiple multipliers are specified,\nthe cost will be multiplied by each of the fields."),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"10 * (5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books {\n title\n }\n}\n")),(0,i.yg)("p",null,"When drilling in further, the cost will go up to ",(0,i.yg)("inlineCode",{parentName:"p"},"240")," since we are now pulling twice as much books and also their authors."),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"20 * (5 + 1 + 5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books(take: 20) {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("p",null,"Notice the nullable ",(0,i.yg)("inlineCode",{parentName:"p"},"$take")," parameter. This might come in handy if ",(0,i.yg)("inlineCode",{parentName:"p"},"take: null"),' means "get all items",\nbut that would also mean that the overall complexity would only be ',(0,i.yg)("inlineCode",{parentName:"p"},"1 + 5 + 1 + 5 + 1 = 11"),",\nwhen in fact that would be a very costly query to execute. "),(0,i.yg)("p",null,"If all of the multiplier fields are either ",(0,i.yg)("inlineCode",{parentName:"p"},"null")," or missing (and don't have default values),\n",(0,i.yg)("inlineCode",{parentName:"p"},"defaultMultiplier")," is used:"),(0,i.yg)("p",null,"Cost calculation: ",(0,i.yg)("inlineCode",{parentName:"p"},"200 * (5 + 1 + 5 + 1)")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n books(take: null) {\n title\n author {\n name\n }\n }\n}\n")),(0,i.yg)("h2",{id:"setup"},"Setup"),(0,i.yg)("p",null,"As with query depth, automatic query complexity is configured through PSR15 middleware:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n// Total query cost cannot exceed 1000 points\n$builder->limitQueryComplexity(1000);\n")),(0,i.yg)("p",null,"Beware that introspection queries would also be limited in complexity. A full introspection\nquery sits at around ",(0,i.yg)("inlineCode",{parentName:"p"},"107")," points, so we recommend a minimum of ",(0,i.yg)("inlineCode",{parentName:"p"},"150")," for query complexity limit."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/673df5d6.49bf875e.js b/assets/js/673df5d6.5d9bf0d1.js similarity index 96% rename from assets/js/673df5d6.49bf875e.js rename to assets/js/673df5d6.5d9bf0d1.js index e816efd716..358e82efe1 100644 --- a/assets/js/673df5d6.49bf875e.js +++ b/assets/js/673df5d6.5d9bf0d1.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7326],{41295:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var r=n(58168),t=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},o=void 0,i={unversionedId:"laravel-package",id:"version-4.1/laravel-package",title:"Getting started with Laravel",description:"The GraphQLite-Laravel package is compatible with Laravel 5.7+, Laravel 6.x and Laravel 7.x.",source:"@site/versioned_docs/version-4.1/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/4.1/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/laravel-package.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},sidebar:"version-4.1/docs",previous:{title:"Symfony bundle",permalink:"/docs/4.1/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/4.1/universal_service_providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function d(e){let{components:a,...n}=e;return(0,t.yg)(h,(0,r.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,t.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,t.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,t.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,t.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,t.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ php artisan vendor:publish --provider=TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider\n")),(0,t.yg)("p",null,"You can then configure the library by editing ",(0,t.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"config/graphqlite.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,t.yg)("p",null,"The debug parameters are detailed in the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,t.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,t.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,t.yg)("code",null,"/graphql")," route is placed under ",(0,t.yg)("code",null,"web")," middleware group which requires a",(0,t.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,t.yg)("p",null,"You have 3 options:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"Use the ",(0,t.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,t.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,t.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,t.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,t.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,t.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,t.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,t.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,t.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"config/graphqlite.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,t.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,t.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,t.yg)("p",null,"Simply add ",(0,t.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,t.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,t.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,t.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,t.yg)("p",null,"If you are planning to use ",(0,t.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,t.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,t.yg)("p",null,"Assuming you are using ",(0,t.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"Sample Apollo client setup with CSRF support")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-js"},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,t.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,t.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,t.yg)("p",null,"By default, the playground will be available at ",(0,t.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,t.yg)("p",null,"Or you can install ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,t.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,t.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,t.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,t.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,t.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7326],{41295:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var r=n(58168),t=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},o=void 0,i={unversionedId:"laravel-package",id:"version-4.1/laravel-package",title:"Getting started with Laravel",description:"The GraphQLite-Laravel package is compatible with Laravel 5.7+, Laravel 6.x and Laravel 7.x.",source:"@site/versioned_docs/version-4.1/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/4.1/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/laravel-package.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},sidebar:"version-4.1/docs",previous:{title:"Symfony bundle",permalink:"/docs/4.1/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/4.1/universal_service_providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...n}=e;return(0,t.yg)(h,(0,r.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,t.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,t.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,t.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,t.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,t.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ php artisan vendor:publish --provider=TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider\n")),(0,t.yg)("p",null,"You can then configure the library by editing ",(0,t.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"config/graphqlite.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,t.yg)("p",null,"The debug parameters are detailed in the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,t.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,t.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,t.yg)("code",null,"/graphql")," route is placed under ",(0,t.yg)("code",null,"web")," middleware group which requires a",(0,t.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,t.yg)("p",null,"You have 3 options:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"Use the ",(0,t.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,t.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,t.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,t.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,t.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,t.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,t.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,t.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,t.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"config/graphqlite.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,t.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,t.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,t.yg)("p",null,"Simply add ",(0,t.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,t.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,t.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,t.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,t.yg)("p",null,"If you are planning to use ",(0,t.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,t.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,t.yg)("p",null,"Assuming you are using ",(0,t.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"Sample Apollo client setup with CSRF support")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-js"},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,t.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,t.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,t.yg)("p",null,"By default, the playground will be available at ",(0,t.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,t.yg)("p",null,"Or you can install ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,t.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,t.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,t.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,t.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,t.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/68b7d615.f5c917b5.js b/assets/js/68b7d615.fbdbb964.js similarity index 99% rename from assets/js/68b7d615.f5c917b5.js rename to assets/js/68b7d615.fbdbb964.js index 33de652b25..c945c64b2e 100644 --- a/assets/js/68b7d615.f5c917b5.js +++ b/assets/js/68b7d615.fbdbb964.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[436],{63881:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>g});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,o={unversionedId:"annotations-reference",id:"version-5.0/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-5.0/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/5.0/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/annotations-reference.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"version-5.0/docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/5.0/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/5.0/semver"}},p={},g=[{value:"@Query annotation",id:"query-annotation",level:2},{value:"@Mutation annotation",id:"mutation-annotation",level:2},{value:"@Type annotation",id:"type-annotation",level:2},{value:"@ExtendType annotation",id:"extendtype-annotation",level:2},{value:"@Input annotation",id:"input-annotation",level:2},{value:"@Field annotation",id:"field-annotation",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"@Logged annotation",id:"logged-annotation",level:2},{value:"@Right annotation",id:"right-annotation",level:2},{value:"@FailWith annotation",id:"failwith-annotation",level:2},{value:"@HideIfUnauthorized annotation",id:"hideifunauthorized-annotation",level:2},{value:"@InjectUser annotation",id:"injectuser-annotation",level:2},{value:"@Security annotation",id:"security-annotation",level:2},{value:"@Factory annotation",id:"factory-annotation",level:2},{value:"@UseInputType annotation",id:"useinputtype-annotation",level:2},{value:"@Decorate annotation",id:"decorate-annotation",level:2},{value:"@Autowire annotation",id:"autowire-annotation",level:2},{value:"@HideParameter annotation",id:"hideparameter-annotation",level:2},{value:"@Validate annotation",id:"validate-annotation",level:2},{value:"@Assertion annotation",id:"assertion-annotation",level:2},{value:"@EnumType annotation",id:"enumtype-annotation",level:2}],y={toc:g},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},y,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query-annotation"},"@Query annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation-annotation"},"@Mutation annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type-annotation"},"@Type annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, ',(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/external-type-declaration"},"the class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype-annotation"},"@ExtendType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input-annotation"},"@Input annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true")," if name is not specified. Whether the annotated PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("em",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation.")))),(0,l.yg)("h2",{id:"field-annotation"},"@Field annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield-annotation"},"@SourceField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield-annotation"},"@MagicField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If not set, no description will be shown.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged-annotation"},"@Logged annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right-annotation"},"@Right annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith-annotation"},"@FailWith annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized-annotation"},"@HideIfUnauthorized annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser-annotation"},"@InjectUser annotation"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security-annotation"},"@Security annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory-annotation"},"@Factory annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype-annotation"},"@UseInputType annotation"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate-annotation"},"@Decorate annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire-annotation"},"@Autowire annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter-annotation"},"@HideParameter annotation"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate-annotation"},"@Validate annotation"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion-annotation"},"@Assertion annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype-annotation"},"@EnumType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[436],{63881:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>g});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},i=void 0,o={unversionedId:"annotations-reference",id:"version-5.0/annotations-reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-5.0/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/5.0/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/annotations-reference.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"annotations-reference",title:"Annotations reference",sidebar_label:"Annotations reference"},sidebar:"version-5.0/docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/5.0/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/5.0/semver"}},p={},g=[{value:"@Query annotation",id:"query-annotation",level:2},{value:"@Mutation annotation",id:"mutation-annotation",level:2},{value:"@Type annotation",id:"type-annotation",level:2},{value:"@ExtendType annotation",id:"extendtype-annotation",level:2},{value:"@Input annotation",id:"input-annotation",level:2},{value:"@Field annotation",id:"field-annotation",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"@Logged annotation",id:"logged-annotation",level:2},{value:"@Right annotation",id:"right-annotation",level:2},{value:"@FailWith annotation",id:"failwith-annotation",level:2},{value:"@HideIfUnauthorized annotation",id:"hideifunauthorized-annotation",level:2},{value:"@InjectUser annotation",id:"injectuser-annotation",level:2},{value:"@Security annotation",id:"security-annotation",level:2},{value:"@Factory annotation",id:"factory-annotation",level:2},{value:"@UseInputType annotation",id:"useinputtype-annotation",level:2},{value:"@Decorate annotation",id:"decorate-annotation",level:2},{value:"@Autowire annotation",id:"autowire-annotation",level:2},{value:"@HideParameter annotation",id:"hideparameter-annotation",level:2},{value:"@Validate annotation",id:"validate-annotation",level:2},{value:"@Assertion annotation",id:"assertion-annotation",level:2},{value:"@EnumType annotation",id:"enumtype-annotation",level:2}],y={toc:g},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},y,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query-annotation"},"@Query annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation-annotation"},"@Mutation annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type-annotation"},"@Type annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, ',(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/external-type-declaration"},"the class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype-annotation"},"@ExtendType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/extend-type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"input-annotation"},"@Input annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input")," annotation is used to declare a GraphQL input type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true")," if name is not specified. Whether the annotated PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"update"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,l.yg)("em",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation.")))),(0,l.yg)("h2",{id:"field-annotation"},"@Field annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Input"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,l.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,l.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"for"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string, array"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/input-types"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,l.yg)("h2",{id:"sourcefield-annotation"},"@SourceField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield-annotation"},"@MagicField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"description"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If not set, no description will be shown.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,l.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged-annotation"},"@Logged annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right-annotation"},"@Right annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith-annotation"},"@FailWith annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized-annotation"},"@HideIfUnauthorized annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser-annotation"},"@InjectUser annotation"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security-annotation"},"@Security annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory-annotation"},"@Factory annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"/docs/5.0/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype-annotation"},"@UseInputType annotation"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate-annotation"},"@Decorate annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire-annotation"},"@Autowire annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter-annotation"},"@HideParameter annotation"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate-annotation"},"@Validate annotation"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion-annotation"},"@Assertion annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype-annotation"},"@EnumType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/68e30702.595a5a15.js b/assets/js/68e30702.7286fff2.js similarity index 95% rename from assets/js/68e30702.595a5a15.js rename to assets/js/68e30702.7286fff2.js index 48e6ad085d..d7d809bce9 100644 --- a/assets/js/68e30702.595a5a15.js +++ b/assets/js/68e30702.7286fff2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3468],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),l=n(23104),o=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),b=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},l,{className:(0,s.A)("tabs__item",y.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",y.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,b.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},24351:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>o,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),l=n(19365);const o={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,u={unversionedId:"prefetch-method",id:"version-7.0.0/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-7.0.0/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/prefetch-method.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"docs",previous:{title:"Query plan",permalink:"/docs/query-plan"},next:{title:"Automatic persisted queries",permalink:"/docs/automatic-persisted-queries"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field]\n public function getUser(#[Prefetch("prefetchUsers")] $prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as first argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public static function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as first argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When a "#',"[Prefetch]",'" attribute is detected on a parameter of "@Field" annotation, the method is called automatically.\nThe prefetch callable must be one of the following:'),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a static method in the same class: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch('prefetchMethod')]")),(0,r.yg)("li",{parentName:"ul"},"a static method in a different class: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch([OtherClass::class, 'prefetchMethod')]")),(0,r.yg)("li",{parentName:"ul"},"a non-static method in a different class, resolvable through the container: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch([OtherService::class, 'prefetchMethod'])]"),"\nThe first argument of the method is always an array of instances of the main type. It can return absolutely anything (mixed).")),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetch methods."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field]\n public function getComments(#[Prefetch("prefetchComments")] $prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public static function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3468],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),l=n(23104),o=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),y=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},l,{className:(0,s.A)("tabs__item",b.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,y.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},24351:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>o,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),l=n(19365);const o={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,u={unversionedId:"prefetch-method",id:"version-7.0.0/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-7.0.0/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/prefetch-method.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"docs",previous:{title:"Query plan",permalink:"/docs/query-plan"},next:{title:"Automatic persisted queries",permalink:"/docs/automatic-persisted-queries"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field]\n public function getUser(#[Prefetch("prefetchUsers")] $prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as first argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public static function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as first argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When a "#',"[Prefetch]",'" attribute is detected on a parameter of "@Field" annotation, the method is called automatically.\nThe prefetch callable must be one of the following:'),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a static method in the same class: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch('prefetchMethod')]")),(0,r.yg)("li",{parentName:"ul"},"a static method in a different class: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch([OtherClass::class, 'prefetchMethod')]")),(0,r.yg)("li",{parentName:"ul"},"a non-static method in a different class, resolvable through the container: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch([OtherService::class, 'prefetchMethod'])]"),"\nThe first argument of the method is always an array of instances of the main type. It can return absolutely anything (mixed).")),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetch methods."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field]\n public function getComments(#[Prefetch("prefetchComments")] $prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public static function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/69f2ab1f.7462786d.js b/assets/js/69f2ab1f.a3628935.js similarity index 98% rename from assets/js/69f2ab1f.7462786d.js rename to assets/js/69f2ab1f.a3628935.js index 8ae1e353d2..38c82add54 100644 --- a/assets/js/69f2ab1f.7462786d.js +++ b/assets/js/69f2ab1f.a3628935.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2784],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),s=a(56347),i=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function g(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,s.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,s]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,u]=m({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=i??p;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&s(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==s&&(p(t),i(n))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":s===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,f.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},93276:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>s,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const s={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},i=void 0,u={unversionedId:"pagination",id:"version-6.0/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-6.0/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/6.0/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/pagination.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},sidebar:"docs",previous:{title:"File uploads",permalink:"/docs/6.0/file-uploads"},next:{title:"Custom types",permalink:"/docs/6.0/custom-types"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(g,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,r.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,r.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,r.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,r.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"You will need to install the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"In your query, simply return a class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,r.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,r.yg)("p",null,'The "count" field returns the ',(0,r.yg)("strong",{parentName:"p"},"total count")," of items."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2784],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),s=a(56347),i=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function g(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,s.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,s]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,u]=m({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=i??p;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&s(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==s&&(p(t),i(n))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":s===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,f.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},93276:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>s,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const s={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},i=void 0,u={unversionedId:"pagination",id:"version-6.0/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-6.0/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/6.0/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/pagination.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},sidebar:"docs",previous:{title:"File uploads",permalink:"/docs/6.0/file-uploads"},next:{title:"Custom types",permalink:"/docs/6.0/custom-types"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(g,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,r.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,r.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,r.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,r.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"You will need to install the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"In your query, simply return a class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,r.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,r.yg)("p",null,'The "count" field returns the ',(0,r.yg)("strong",{parentName:"p"},"total count")," of items."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6a8c9872.66115f2f.js b/assets/js/6a8c9872.bbc23cdd.js similarity index 98% rename from assets/js/6a8c9872.66115f2f.js rename to assets/js/6a8c9872.bbc23cdd.js index dcf5e6c3ef..bf886d6233 100644 --- a/assets/js/6a8c9872.66115f2f.js +++ b/assets/js/6a8c9872.bbc23cdd.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8087],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),u=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==u&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",b.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},74433:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>u,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const u={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-7.0.0/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-7.0.0/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/query-plan.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"docs",previous:{title:"Operation complexity",permalink:"/docs/operation-complexity"},next:{title:"Prefetching records",permalink:"/docs/prefetch-method"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8087],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),u=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==u&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",b.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},74433:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>u,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const u={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-7.0.0/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-7.0.0/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/query-plan.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"docs",previous:{title:"Operation complexity",permalink:"/docs/operation-complexity"},next:{title:"Prefetching records",permalink:"/docs/prefetch-method"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6ad31330.8b092014.js b/assets/js/6ad31330.6e65fbf9.js similarity index 83% rename from assets/js/6ad31330.8b092014.js rename to assets/js/6ad31330.6e65fbf9.js index 50973d2b4e..657c40b327 100644 --- a/assets/js/6ad31330.8b092014.js +++ b/assets/js/6ad31330.6e65fbf9.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6730],{8119:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>c,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},o=void 0,s={unversionedId:"getting-started",id:"version-5.0/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-5.0/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/5.0/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/getting-started.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},sidebar:"version-5.0/docs",previous:{title:"GraphQLite",permalink:"/docs/5.0/"},next:{title:"Symfony bundle",permalink:"/docs/5.0/symfony-bundle"}},d={},l=[],p={toc:l},g="wrapper";function c(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,r.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/universal-service-providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/other-frameworks"},"Get started with another framework (or no framework)"))))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6730],{8119:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>p,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},o=void 0,s={unversionedId:"getting-started",id:"version-5.0/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-5.0/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/5.0/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/getting-started.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},sidebar:"version-5.0/docs",previous:{title:"GraphQLite",permalink:"/docs/5.0/"},next:{title:"Symfony bundle",permalink:"/docs/5.0/symfony-bundle"}},d={},l=[],c={toc:l},g="wrapper";function p(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,r.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/universal-service-providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/other-frameworks"},"Get started with another framework (or no framework)"))))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6c124661.83050f3b.js b/assets/js/6c124661.afc216c8.js similarity index 97% rename from assets/js/6c124661.83050f3b.js rename to assets/js/6c124661.afc216c8.js index a4fcbe5b4f..9a12009ef1 100644 --- a/assets/js/6c124661.83050f3b.js +++ b/assets/js/6c124661.afc216c8.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5762],{47935:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>n,default:()=>h,frontMatter:()=>i,metadata:()=>r,toc:()=>s});var o=t(58168),l=(t(96540),t(15680));t(67443);const i={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},n=void 0,r={unversionedId:"file-uploads",id:"file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/docs/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/next/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/file-uploads.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"docs",previous:{title:"Automatic persisted queries",permalink:"/docs/next/automatic-persisted-queries"},next:{title:"Pagination",permalink:"/docs/next/pagination"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],u={toc:s},d="wrapper";function h(e){let{components:a,...t}=e;return(0,l.yg)(d,(0,o.A)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,l.yg)("h2",{id:"installation"},"Installation"),(0,l.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,l.yg)("p",null,"You must start by installing this package:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,l.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,l.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,l.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,l.yg)("p",null,"In order to use this, you must first be sure that the ",(0,l.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,l.yg)("p",null,"Simply add ",(0,l.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,l.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,l.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,l.yg)("h2",{id:"usage"},"Usage"),(0,l.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,l.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")),(0,l.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,l.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5762],{47935:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>n,default:()=>h,frontMatter:()=>i,metadata:()=>r,toc:()=>s});var o=t(58168),l=(t(96540),t(15680));t(67443);const i={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},n=void 0,r={unversionedId:"file-uploads",id:"file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/docs/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/next/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/file-uploads.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"docs",previous:{title:"Automatic persisted queries",permalink:"/docs/next/automatic-persisted-queries"},next:{title:"Pagination",permalink:"/docs/next/pagination"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],u={toc:s},d="wrapper";function h(e){let{components:a,...t}=e;return(0,l.yg)(d,(0,o.A)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,l.yg)("h2",{id:"installation"},"Installation"),(0,l.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,l.yg)("p",null,"You must start by installing this package:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,l.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,l.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,l.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,l.yg)("p",null,"In order to use this, you must first be sure that the ",(0,l.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,l.yg)("p",null,"Simply add ",(0,l.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,l.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,l.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,l.yg)("h2",{id:"usage"},"Usage"),(0,l.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,l.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")),(0,l.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,l.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6c14a231.3eaaec7d.js b/assets/js/6c14a231.2ab795f0.js similarity index 88% rename from assets/js/6c14a231.3eaaec7d.js rename to assets/js/6c14a231.2ab795f0.js index 25e3c8b721..81916364ca 100644 --- a/assets/js/6c14a231.3eaaec7d.js +++ b/assets/js/6c14a231.2ab795f0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1188],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=p(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),b=(()=>{const e=s??d;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==o&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,l.A)("tabs__item",y.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(f,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function T(e){const t=(0,b.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},81087:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},s=void 0,i={unversionedId:"index",id:"version-5.0/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-5.0/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/5.0/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/README.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"version-5.0/docs",next:{title:"Getting Started",permalink:"/docs/5.0/getting-started"}},c={},d=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],p={toc:d},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1188],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),b=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==o&&(p(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,l.A)("tabs__item",y.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(f,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function T(e){const t=(0,b.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},81087:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},s=void 0,i={unversionedId:"index",id:"version-5.0/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-5.0/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/5.0/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/README.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"version-5.0/docs",next:{title:"Getting Started",permalink:"/docs/5.0/getting-started"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:p},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6c4340be.2f08e357.js b/assets/js/6c4340be.43dfaea6.js similarity index 99% rename from assets/js/6c4340be.2f08e357.js rename to assets/js/6c4340be.43dfaea6.js index 0e1b1f5fb4..43a08720fa 100644 --- a/assets/js/6c4340be.2f08e357.js +++ b/assets/js/6c4340be.43dfaea6.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8350],{19365:(e,n,a)=>{a.d(n,{A:()=>r});var t=a(96540),p=a(20053);const l={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:a,className:r}=e;return t.createElement("div",{role:"tabpanel",className:(0,p.A)(l.tabItem,r),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>N});var t=a(58168),p=a(96540),l=a(20053),r=a(23104),s=a(56347),i=a(57485),u=a(31682),o=a(89466);function c(e){return function(e){return p.Children.map(e,(e=>{if(!e||(0,p.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:p}}=e;return{value:n,label:a,attributes:t,default:p}}))}function y(e){const{values:n,children:a}=e;return(0,p.useMemo)((()=>{const e=n??c(a);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function m(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function d(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,i.aZ)(l),(0,p.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function g(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=y(e),[r,s]=(0,p.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[i,u]=d({queryString:a,groupId:t}),[c,g]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,p.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=i??c;return m({value:e,tabValues:l})?e:null})();(0,p.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:r,selectValue:(0,p.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const o=[],{blockElementScrollPositionUntilNextRender:c}=(0,r.a_)(),y=e=>{const n=e.currentTarget,a=o.indexOf(n),t=u[a].value;t!==s&&(c(n),i(t))},m=e=>{let n=null;switch(e.key){case"Enter":y(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return p.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},u.map((e=>{let{value:n,label:a,attributes:r}=e;return p.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:m,onClick:y},r,{className:(0,l.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,p.cloneElement)(e,{className:"margin-top--md"}):null}return p.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,p.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function T(e){const n=g(e);return p.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},p.createElement(b,(0,t.A)({},e,n)),p.createElement(v,(0,t.A)({},e,n)))}function N(e){const n=(0,h.A)();return p.createElement(T,(0,t.A)({key:String(n)},e))}},60384:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>u,toc:()=>c});var t=a(58168),p=(a(96540),a(15680)),l=(a(67443),a(11470)),r=a(19365);const s={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},i=void 0,u={unversionedId:"type-mapping",id:"version-7.0.0/type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-7.0.0/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/type-mapping.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"docs",previous:{title:"Subscriptions",permalink:"/docs/subscriptions"},next:{title:"Autowiring services",permalink:"/docs/autowiring"}},o={},c=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Enum types with myclabs/php-enum",id:"enum-types-with-myclabsphp-enum",level:3},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],y={toc:c},m="wrapper";function d(e){let{components:n,...a}=e;return(0,p.yg)(m,(0,t.A)({},y,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(name="MyProduct")\n */\nclass Product { /* ... */ }\n')))),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')))),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')))),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")))),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")))),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")))),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"Union types for return are supported in GraphQLite as of version 6.0:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\npublic function companyOrContact(int $id): Company|Contact\n{\n // Some code that returns a company or a contact.\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")))),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("p",null,"PHP 8.1 introduced native support for Enums. GraphQLite now also supports native enums as of version 5.1."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nenum Status: string\n{\n case ON = 'on';\n case OFF = 'off';\n case PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return User[]\n */\n#[Query]\npublic function users(Status $status): array\n{\n if ($status === Status::ON) {\n // Your logic\n }\n // ...\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: Status!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"name")," property on the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," annotation:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'namespace Model\\User;\n\n#[Type(name: "UserStatus")]\nenum Status: string\n{\n // ...\n}\n')),(0,p.yg)("h3",{id:"enum-types-with-myclabsphp-enum"},"Enum types with myclabs/php-enum"),(0,p.yg)("div",{class:"alert alert--danger"},"This implementation is now deprecated and will be removed in the future. You are advised to use native enums instead."),(0,p.yg)("p",null,(0,p.yg)("em",{parentName:"p"},"Prior to version 5.1, GraphQLite only supported Enums through the 3rd party library, ",(0,p.yg)("a",{parentName:"em",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),". If you'd like to use this implementation you'll first need to add this library as a dependency to your application.")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[]\n */\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')))),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n/**\n * @EnumType(name="UserStatus")\n */\nclass StatusEnum extends Enum\n{\n // ...\n}\n')))),(0,p.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,p.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,p.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,p.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation. Note that a description (reason) is required for the annotation to be rendered."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n * @deprecated use field `name` instead\n */\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,p.yg)("p",null,"This will add the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,p.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,p.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,p.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,p.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,p.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8350],{19365:(e,n,a)=>{a.d(n,{A:()=>r});var t=a(96540),p=a(20053);const l={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:a,className:r}=e;return t.createElement("div",{role:"tabpanel",className:(0,p.A)(l.tabItem,r),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>N});var t=a(58168),p=a(96540),l=a(20053),r=a(23104),s=a(56347),i=a(57485),u=a(31682),o=a(89466);function c(e){return function(e){return p.Children.map(e,(e=>{if(!e||(0,p.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:p}}=e;return{value:n,label:a,attributes:t,default:p}}))}function y(e){const{values:n,children:a}=e;return(0,p.useMemo)((()=>{const e=n??c(a);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function m(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function d(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,i.aZ)(l),(0,p.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function g(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=y(e),[r,s]=(0,p.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[i,u]=d({queryString:a,groupId:t}),[c,g]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,p.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=i??c;return m({value:e,tabValues:l})?e:null})();(0,p.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:r,selectValue:(0,p.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const o=[],{blockElementScrollPositionUntilNextRender:c}=(0,r.a_)(),y=e=>{const n=e.currentTarget,a=o.indexOf(n),t=u[a].value;t!==s&&(c(n),i(t))},m=e=>{let n=null;switch(e.key){case"Enter":y(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return p.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},u.map((e=>{let{value:n,label:a,attributes:r}=e;return p.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:m,onClick:y},r,{className:(0,l.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,p.cloneElement)(e,{className:"margin-top--md"}):null}return p.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,p.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function T(e){const n=g(e);return p.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},p.createElement(b,(0,t.A)({},e,n)),p.createElement(v,(0,t.A)({},e,n)))}function N(e){const n=(0,h.A)();return p.createElement(T,(0,t.A)({key:String(n)},e))}},60384:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>u,toc:()=>c});var t=a(58168),p=(a(96540),a(15680)),l=(a(67443),a(11470)),r=a(19365);const s={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},i=void 0,u={unversionedId:"type-mapping",id:"version-7.0.0/type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-7.0.0/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/type-mapping.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"docs",previous:{title:"Subscriptions",permalink:"/docs/subscriptions"},next:{title:"Autowiring services",permalink:"/docs/autowiring"}},o={},c=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Enum types with myclabs/php-enum",id:"enum-types-with-myclabsphp-enum",level:3},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],y={toc:c},m="wrapper";function d(e){let{components:n,...a}=e;return(0,p.yg)(m,(0,t.A)({},y,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(name="MyProduct")\n */\nclass Product { /* ... */ }\n')))),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')))),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')))),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")))),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")))),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")))),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"Union types for return are supported in GraphQLite as of version 6.0:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\npublic function companyOrContact(int $id): Company|Contact\n{\n // Some code that returns a company or a contact.\n}\n"))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")))),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("p",null,"PHP 8.1 introduced native support for Enums. GraphQLite now also supports native enums as of version 5.1."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nenum Status: string\n{\n case ON = 'on';\n case OFF = 'off';\n case PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return User[]\n */\n#[Query]\npublic function users(Status $status): array\n{\n if ($status === Status::ON) {\n // Your logic\n }\n // ...\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: Status!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"name")," property on the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," annotation:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'namespace Model\\User;\n\n#[Type(name: "UserStatus")]\nenum Status: string\n{\n // ...\n}\n')),(0,p.yg)("h3",{id:"enum-types-with-myclabsphp-enum"},"Enum types with myclabs/php-enum"),(0,p.yg)("div",{class:"alert alert--danger"},"This implementation is now deprecated and will be removed in the future. You are advised to use native enums instead."),(0,p.yg)("p",null,(0,p.yg)("em",{parentName:"p"},"Prior to version 5.1, GraphQLite only supported Enums through the 3rd party library, ",(0,p.yg)("a",{parentName:"em",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),". If you'd like to use this implementation you'll first need to add this library as a dependency to your application.")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[]\n */\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')))),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,p.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,p.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n'))),(0,p.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n/**\n * @EnumType(name="UserStatus")\n */\nclass StatusEnum extends Enum\n{\n // ...\n}\n')))),(0,p.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,p.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,p.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,p.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation. Note that a description (reason) is required for the annotation to be rendered."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n * @deprecated use field `name` instead\n */\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,p.yg)("p",null,"This will add the ",(0,p.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,p.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,p.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,p.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,p.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,p.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6c6ce37c.0aab0791.js b/assets/js/6c6ce37c.8f5946ca.js similarity index 88% rename from assets/js/6c6ce37c.0aab0791.js rename to assets/js/6c6ce37c.8f5946ca.js index a05eba0fde..62a41e4d6f 100644 --- a/assets/js/6c6ce37c.0aab0791.js +++ b/assets/js/6c6ce37c.8f5946ca.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9445],{60388:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-4.2/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-4.2/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/4.2/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/troubleshooting.md",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"version-4.2/docs",previous:{title:"Internals",permalink:"/docs/4.2/internals"},next:{title:"Migrating",permalink:"/docs/4.2/migrating"}},l={},u=[],p={toc:u},c="wrapper";function d(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,n.A)({},p,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9445],{60388:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>g,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-4.2/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-4.2/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/4.2/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/troubleshooting.md",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"version-4.2/docs",previous:{title:"Internals",permalink:"/docs/4.2/internals"},next:{title:"Migrating",permalink:"/docs/4.2/migrating"}},l={},u=[],p={toc:u},c="wrapper";function g(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,n.A)({},p,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6cfcfcfb.87fe7d5d.js b/assets/js/6cfcfcfb.bea63401.js similarity index 98% rename from assets/js/6cfcfcfb.87fe7d5d.js rename to assets/js/6cfcfcfb.bea63401.js index ee5279cc7f..94a55dbf1d 100644 --- a/assets/js/6cfcfcfb.87fe7d5d.js +++ b/assets/js/6cfcfcfb.bea63401.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8798],{77660:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>r,default:()=>y,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var t=a(58168),p=(a(96540),a(15680));a(67443);const i={id:"type_mapping",title:"Type mapping",sidebar_label:"Type mapping",original_id:"type_mapping"},r=void 0,l={unversionedId:"type_mapping",id:"version-3.0/type_mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-3.0/type_mapping.mdx",sourceDirName:".",slug:"/type_mapping",permalink:"/docs/3.0/type_mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/type_mapping.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"type_mapping",title:"Type mapping",sidebar_label:"Type mapping",original_id:"type_mapping"},sidebar:"version-3.0/docs",previous:{title:"Mutations",permalink:"/docs/3.0/mutations"},next:{title:"Extending a type",permalink:"/docs/3.0/extend_type"}},o={},s=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2}],u={toc:s},g="wrapper";function y(e){let{components:n,...a}=e;return(0,p.yg)(g,(0,t.A)({},u,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/3.0/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/3.0/custom-output-types"},"custom output types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"You can create a GraphQL union type ",(0,p.yg)("em",{parentName:"p"},"on the fly")," using the pipe ",(0,p.yg)("inlineCode",{parentName:"p"},"|")," operator in the PHPDoc:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact <== can return a company OR a contact.\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8798],{77660:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>r,default:()=>y,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var t=a(58168),p=(a(96540),a(15680));a(67443);const i={id:"type_mapping",title:"Type mapping",sidebar_label:"Type mapping",original_id:"type_mapping"},r=void 0,l={unversionedId:"type_mapping",id:"version-3.0/type_mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-3.0/type_mapping.mdx",sourceDirName:".",slug:"/type_mapping",permalink:"/docs/3.0/type_mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/type_mapping.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"type_mapping",title:"Type mapping",sidebar_label:"Type mapping",original_id:"type_mapping"},sidebar:"version-3.0/docs",previous:{title:"Mutations",permalink:"/docs/3.0/mutations"},next:{title:"Extending a type",permalink:"/docs/3.0/extend_type"}},o={},s=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2}],u={toc:s},g="wrapper";function y(e){let{components:n,...a}=e;return(0,p.yg)(g,(0,t.A)({},u,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/3.0/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/3.0/custom-output-types"},"custom output types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"You can create a GraphQL union type ",(0,p.yg)("em",{parentName:"p"},"on the fly")," using the pipe ",(0,p.yg)("inlineCode",{parentName:"p"},"|")," operator in the PHPDoc:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact <== can return a company OR a contact.\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6d89025c.b6142cc1.js b/assets/js/6d89025c.c42687f4.js similarity index 99% rename from assets/js/6d89025c.b6142cc1.js rename to assets/js/6d89025c.c42687f4.js index 7b337db672..8bdb7eb359 100644 --- a/assets/js/6d89025c.b6142cc1.js +++ b/assets/js/6d89025c.c42687f4.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1190],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},62141:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-6.0/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.0/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/6.0/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/symfony-bundle-advanced.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"docs",previous:{title:"Class with multiple output types",permalink:"/docs/6.0/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/6.0/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1190],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},62141:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-6.0/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.0/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/6.0/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/symfony-bundle-advanced.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"docs",previous:{title:"Class with multiple output types",permalink:"/docs/6.0/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/6.0/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6efd6ec9.a781fcb6.js b/assets/js/6efd6ec9.d16f9c24.js similarity index 98% rename from assets/js/6efd6ec9.a781fcb6.js rename to assets/js/6efd6ec9.d16f9c24.js index 607fa56c46..3eaad91e3d 100644 --- a/assets/js/6efd6ec9.a781fcb6.js +++ b/assets/js/6efd6ec9.d16f9c24.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4805],{96421:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>m,frontMatter:()=>i,metadata:()=>s,toc:()=>o});var t=a(58168),p=(a(96540),a(15680));a(67443);const i={id:"type_mapping",title:"Type mapping",sidebar_label:"Type mapping",original_id:"type_mapping"},r=void 0,s={unversionedId:"type_mapping",id:"version-4.0/type_mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-4.0/type_mapping.mdx",sourceDirName:".",slug:"/type_mapping",permalink:"/docs/4.0/type_mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/type_mapping.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"type_mapping",title:"Type mapping",sidebar_label:"Type mapping",original_id:"type_mapping"},sidebar:"version-4.0/docs",previous:{title:"Mutations",permalink:"/docs/4.0/mutations"},next:{title:"Autowiring services",permalink:"/docs/4.0/autowiring"}},l={},o=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],u={toc:o},y="wrapper";function m(e){let{components:n,...a}=e;return(0,p.yg)(y,(0,t.A)({},u,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/4.0/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(name="MyProduct")\n */\nclass Product { /* ... */ }\n')),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/4.0/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"You can create a GraphQL union type ",(0,p.yg)("em",{parentName:"p"},"on the fly")," using the pipe ",(0,p.yg)("inlineCode",{parentName:"p"},"|")," operator in the PHPDoc:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact <== can return a company OR a contact.\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,"PHP has no native support for enum types. Hopefully, there are a number of PHP libraries that emulate enums in PHP.\nThe most commonly used library is ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum")," and GraphQLite comes with\nnative support for it."),(0,p.yg)("p",null,"You will first need to install ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[]\n */\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n/**\n * @EnumType(name="UserStatus")\n */\nclass StatusEnum extends Enum\n{\n // ...\n}\n')),(0,p.yg)("div",{class:"alert alert--info"},'There are many enumeration library in PHP and you might be using another library. If you want to add support for your own library, this is not extremely difficult to do. You need to register a custom "RootTypeMapper" with GraphQLite. You can learn more about ',(0,p.yg)("em",null,"type mappers")," in the ",(0,p.yg)("a",{href:"internals"},'"internals" documentation'),"and ",(0,p.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/MyCLabsEnumTypeMapper.php"},"copy/paste and adapt the root type mapper used for myclabs/php-enum"),"."),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4805],{96421:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>m,frontMatter:()=>i,metadata:()=>s,toc:()=>o});var t=a(58168),p=(a(96540),a(15680));a(67443);const i={id:"type_mapping",title:"Type mapping",sidebar_label:"Type mapping",original_id:"type_mapping"},r=void 0,s={unversionedId:"type_mapping",id:"version-4.0/type_mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-4.0/type_mapping.mdx",sourceDirName:".",slug:"/type_mapping",permalink:"/docs/4.0/type_mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/type_mapping.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"type_mapping",title:"Type mapping",sidebar_label:"Type mapping",original_id:"type_mapping"},sidebar:"version-4.0/docs",previous:{title:"Mutations",permalink:"/docs/4.0/mutations"},next:{title:"Autowiring services",permalink:"/docs/4.0/autowiring"}},l={},o=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],u={toc:o},y="wrapper";function m(e){let{components:n,...a}=e;return(0,p.yg)(y,(0,t.A)({},u,a,{components:n,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"As explained in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/4.0/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,p.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,p.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"string")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"int")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"bool")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"float"))),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,p.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,p.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,p.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,p.yg)("p",null,(0,p.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,p.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,p.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(name="MyProduct")\n */\nclass Product { /* ... */ }\n')),(0,p.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,p.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,p.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,p.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,p.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,p.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[] <=== we specify that the array is an array of User objects.\n */\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')),(0,p.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,p.yg)("p",null,"GraphQL comes with a native ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,p.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,p.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field(outputType="ID")\n */\npublic function getId(): string\n{\n // ...\n}\n')),(0,p.yg)("p",null,"Using the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,p.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,p.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,p.yg)("p",null,"You can learn more about forcing output types in the ",(0,p.yg)("a",{parentName:"p",href:"/docs/4.0/custom-types"},"custom types section"),"."),(0,p.yg)("h3",{id:"id-class"},"ID class"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Field\n */\npublic function getId(): ID\n{\n // ...\n}\n")),(0,p.yg)("p",null,"Note that you can also use the ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n/**\n * @Mutation\n */\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")),(0,p.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,p.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,p.yg)("p",null,"When used as an output type, ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n */\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,p.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,p.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,p.yg)("div",{class:"alert alert--danger"},"PHP ",(0,p.yg)("code",null,"DateTime")," type is not supported."),(0,p.yg)("h2",{id:"union-types"},"Union types"),(0,p.yg)("p",null,"You can create a GraphQL union type ",(0,p.yg)("em",{parentName:"p"},"on the fly")," using the pipe ",(0,p.yg)("inlineCode",{parentName:"p"},"|")," operator in the PHPDoc:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Company|Contact <== can return a company OR a contact.\n */\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")),(0,p.yg)("h2",{id:"enum-types"},"Enum types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,"PHP has no native support for enum types. Hopefully, there are a number of PHP libraries that emulate enums in PHP.\nThe most commonly used library is ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum")," and GraphQLite comes with\nnative support for it."),(0,p.yg)("p",null,"You will first need to install ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,p.yg)("p",null,"Now, any class extending the ",(0,p.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @return User[]\n */\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,p.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,p.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n/**\n * @EnumType(name="UserStatus")\n */\nclass StatusEnum extends Enum\n{\n // ...\n}\n')),(0,p.yg)("div",{class:"alert alert--info"},'There are many enumeration library in PHP and you might be using another library. If you want to add support for your own library, this is not extremely difficult to do. You need to register a custom "RootTypeMapper" with GraphQLite. You can learn more about ',(0,p.yg)("em",null,"type mappers")," in the ",(0,p.yg)("a",{href:"internals"},'"internals" documentation'),"and ",(0,p.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/MyCLabsEnumTypeMapper.php"},"copy/paste and adapt the root type mapper used for myclabs/php-enum"),"."),(0,p.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,p.yg)("small",null,"Available in GraphQLite 4.0+"),(0,p.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,p.yg)("p",null,"If you need more types, you can check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,p.yg)("p",null,"Or if you have some special needs, ",(0,p.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6fe30f11.a00d78f9.js b/assets/js/6fe30f11.3c83de5b.js similarity index 85% rename from assets/js/6fe30f11.a00d78f9.js rename to assets/js/6fe30f11.3c83de5b.js index 68d9a5c31a..73141cb7c7 100644 --- a/assets/js/6fe30f11.a00d78f9.js +++ b/assets/js/6fe30f11.3c83de5b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5235],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),o=n(23104),l=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),b=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{b&&l(b)}),[b]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:l,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},o,{className:(0,s.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",y.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,b.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},90488:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),o=n(19365);const l={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,u={unversionedId:"prefetch-method",id:"version-3.0/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-3.0/prefetch_method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/3.0/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/prefetch_method.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))),(0,r.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5235],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),l=n(23104),o=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),b=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},l,{className:(0,s.A)("tabs__item",y.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",y.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,b.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},90488:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>o,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),l=n(19365);const o={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,u={unversionedId:"prefetch-method",id:"version-3.0/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-3.0/prefetch_method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/3.0/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/prefetch_method.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))),(0,r.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/71a56230.f3946764.js b/assets/js/71a56230.2a30f758.js similarity index 89% rename from assets/js/71a56230.f3946764.js rename to assets/js/71a56230.2a30f758.js index 4c0e018386..4d775d3988 100644 --- a/assets/js/71a56230.f3946764.js +++ b/assets/js/71a56230.2a30f758.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3332],{11277:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting",original_id:"troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-4.1/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-4.1/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/4.1/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/troubleshooting.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting",original_id:"troubleshooting"},sidebar:"version-4.1/docs",previous:{title:"Internals",permalink:"/docs/4.1/internals"},next:{title:"Migrating",permalink:"/docs/4.1/migrating"}},l={},u=[],g={toc:u},p="wrapper";function d(e){let{components:t,...o}=e;return(0,r.yg)(p,(0,n.A)({},g,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3332],{11277:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>c,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting",original_id:"troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-4.1/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-4.1/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/4.1/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/troubleshooting.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting",original_id:"troubleshooting"},sidebar:"version-4.1/docs",previous:{title:"Internals",permalink:"/docs/4.1/internals"},next:{title:"Migrating",permalink:"/docs/4.1/migrating"}},l={},u=[],g={toc:u},p="wrapper";function c(e){let{components:t,...o}=e;return(0,r.yg)(p,(0,n.A)({},g,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/72be5fd7.8272dd94.js b/assets/js/72be5fd7.847f2c01.js similarity index 98% rename from assets/js/72be5fd7.8272dd94.js rename to assets/js/72be5fd7.847f2c01.js index 54403b36b9..2c44cd3745 100644 --- a/assets/js/72be5fd7.8272dd94.js +++ b/assets/js/72be5fd7.847f2c01.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7045],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},42209:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},u=void 0,s={unversionedId:"extend-input-type",id:"version-7.0.0/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-7.0.0/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/extend-input-type.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"docs",previous:{title:"Custom argument resolving",permalink:"/docs/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/multiple-output-types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7045],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},42209:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},u=void 0,s={unversionedId:"extend-input-type",id:"version-7.0.0/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-7.0.0/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/extend-input-type.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"docs",previous:{title:"Custom argument resolving",permalink:"/docs/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/multiple-output-types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/72d0dc3a.74aee997.js b/assets/js/72d0dc3a.19800fb7.js similarity index 88% rename from assets/js/72d0dc3a.74aee997.js rename to assets/js/72d0dc3a.19800fb7.js index 66312c0607..623ec022be 100644 --- a/assets/js/72d0dc3a.74aee997.js +++ b/assets/js/72d0dc3a.19800fb7.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7035],{50983:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-4.3/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-4.3/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/4.3/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/troubleshooting.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"version-4.3/docs",previous:{title:"Internals",permalink:"/docs/4.3/internals"},next:{title:"Migrating",permalink:"/docs/4.3/migrating"}},l={},u=[],p={toc:u},c="wrapper";function d(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,n.A)({},p,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7035],{50983:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>g,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-4.3/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-4.3/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/4.3/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/troubleshooting.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"version-4.3/docs",previous:{title:"Internals",permalink:"/docs/4.3/internals"},next:{title:"Migrating",permalink:"/docs/4.3/migrating"}},l={},u=[],p={toc:u},c="wrapper";function g(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,n.A)({},p,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/741df2ae.f14df0a1.js b/assets/js/741df2ae.ba6bdd66.js similarity index 98% rename from assets/js/741df2ae.f14df0a1.js rename to assets/js/741df2ae.ba6bdd66.js index 5c0dd26d38..68cc09f9e5 100644 --- a/assets/js/741df2ae.f14df0a1.js +++ b/assets/js/741df2ae.ba6bdd66.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6095],{88948:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>o,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,l={unversionedId:"internals",id:"internals",title:"Internals",description:"Mapping types",source:"@site/docs/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/next/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/internals.md",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"docs",previous:{title:"Laravel specific features",permalink:"/docs/next/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/next/troubleshooting"}},o={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types (Deprecated: use native enums)"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"EnumTypeMapper"),": maps PHP enums to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Type]")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"#[ExtendType]")," attribute and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/next/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6095],{88948:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>o,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,l={unversionedId:"internals",id:"internals",title:"Internals",description:"Mapping types",source:"@site/docs/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/next/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/internals.md",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"docs",previous:{title:"Laravel specific features",permalink:"/docs/next/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/next/troubleshooting"}},o={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types (Deprecated: use native enums)"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"EnumTypeMapper"),": maps PHP enums to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Type]")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"#[ExtendType]")," attribute and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/next/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/74383bd8.10b03c4d.js b/assets/js/74383bd8.4d536fa8.js similarity index 96% rename from assets/js/74383bd8.10b03c4d.js rename to assets/js/74383bd8.4d536fa8.js index 3cf7637545..86c4aea40b 100644 --- a/assets/js/74383bd8.10b03c4d.js +++ b/assets/js/74383bd8.4d536fa8.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4884],{93047:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,o={unversionedId:"internals",id:"version-6.1/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-6.1/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/6.1/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/internals.md",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"docs",previous:{title:"Laravel specific features",permalink:"/docs/6.1/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/6.1/troubleshooting"}},l={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types (Deprecated: use native enums)"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"EnumTypeMapper"),": maps PHP enums to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/6.1/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4884],{93047:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>o,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,l={unversionedId:"internals",id:"version-6.1/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-6.1/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/6.1/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/internals.md",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"docs",previous:{title:"Laravel specific features",permalink:"/docs/6.1/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/6.1/troubleshooting"}},o={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types (Deprecated: use native enums)"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"EnumTypeMapper"),": maps PHP enums to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/6.1/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7515d7ec.0f02db8c.js b/assets/js/7515d7ec.500009da.js similarity index 99% rename from assets/js/7515d7ec.0f02db8c.js rename to assets/js/7515d7ec.500009da.js index da2c2e82d2..d7903233d7 100644 --- a/assets/js/7515d7ec.0f02db8c.js +++ b/assets/js/7515d7ec.500009da.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9870],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),l=t(20053),o=t(23104),s=t(56347),i=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,s.W6)(),l=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(n.location.search);a.set(l,e),n.replace({...n.location,search:a.toString()})}),[l,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,l=g(e),[o,s]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:l}))),[i,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,l]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:n}),m=(()=>{const e=i??c;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&s(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:s,selectValue:i,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==s&&(c(a),i(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:s===a?0:-1,"aria-selected":s===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":s===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},34991:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>i,default:()=>y,frontMatter:()=>s,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),o=t(19365);const s={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},i=void 0,u={unversionedId:"fine-grained-security",id:"version-3.0/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-3.0/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/3.0/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/fine-grained-security.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/authentication_authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"PHP 7")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/authentication_authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9870],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),l=t(20053),o=t(23104),s=t(56347),i=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,s.W6)(),l=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(n.location.search);a.set(l,e),n.replace({...n.location,search:a.toString()})}),[l,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,l=g(e),[o,s]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:l}))),[i,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,l]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:n}),m=(()=>{const e=i??c;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&s(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:s,selectValue:i,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==s&&(c(a),i(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:s===a?0:-1,"aria-selected":s===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":s===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},34991:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>i,default:()=>y,frontMatter:()=>s,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),o=t(19365);const s={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},i=void 0,u={unversionedId:"fine-grained-security",id:"version-3.0/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-3.0/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/3.0/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/fine-grained-security.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/authentication_authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"PHP 7")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/authentication_authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/3.0/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/756c6ac7.d29667a5.js b/assets/js/756c6ac7.55eadee8.js similarity index 99% rename from assets/js/756c6ac7.d29667a5.js rename to assets/js/756c6ac7.55eadee8.js index adb22c378d..b1f4edc66a 100644 --- a/assets/js/756c6ac7.d29667a5.js +++ b/assets/js/756c6ac7.55eadee8.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3419],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},30768:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-4.3/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.3/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/4.3/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/symfony-bundle-advanced.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"version-4.3/docs",previous:{title:"Class with multiple output types",permalink:"/docs/4.3/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/4.3/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3419],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},30768:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-4.3/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.3/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/4.3/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/symfony-bundle-advanced.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"version-4.3/docs",previous:{title:"Class with multiple output types",permalink:"/docs/4.3/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/4.3/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/75cc8326.67419917.js b/assets/js/75cc8326.0b5dad68.js similarity index 98% rename from assets/js/75cc8326.67419917.js rename to assets/js/75cc8326.0b5dad68.js index e55e927756..6ac8b8dc29 100644 --- a/assets/js/75cc8326.67419917.js +++ b/assets/js/75cc8326.0b5dad68.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8718],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),u=a(56347),i=a(57485),s=a(31682),p=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function f(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=c(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,s]=h({queryString:a,groupId:n}),[d,f]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),g=(()=>{const e=i??d;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{g&&u(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),s(e),f(e)}),[s,f,l]),tabValues:l}}var g=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:u,selectValue:i,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==u&&(d(t),i(n))},m=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:c},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":u===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},94780:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>s,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const u={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},i=void 0,s={unversionedId:"file-uploads",id:"version-4.1/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-4.1/file_uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/4.1/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/file_uploads.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},sidebar:"version-4.1/docs",previous:{title:"Prefetching records",permalink:"/docs/4.1/prefetch-method"},next:{title:"Pagination",permalink:"/docs/4.1/pagination"}},p={},d=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:2},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:2},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:2},{value:"Usage",id:"usage",level:2}],c={toc:d},m="wrapper";function h(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,r.yg)("p",null,"You must start by installing this package:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,r.yg)("h2",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,r.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,r.yg)("h2",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,r.yg)("p",null,"In order to use this, you must first be sure that the ",(0,r.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,r.yg)("h2",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,r.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,r.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")))),(0,r.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,r.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8718],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),u=a(56347),i=a(57485),s=a(31682),p=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function f(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=c(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,s]=h({queryString:a,groupId:n}),[d,f]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),g=(()=>{const e=i??d;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{g&&u(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),s(e),f(e)}),[s,f,l]),tabValues:l}}var g=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:u,selectValue:i,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==u&&(d(t),i(n))},m=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:c},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":u===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},94780:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>s,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const u={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},i=void 0,s={unversionedId:"file-uploads",id:"version-4.1/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-4.1/file_uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/4.1/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/file_uploads.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},sidebar:"version-4.1/docs",previous:{title:"Prefetching records",permalink:"/docs/4.1/prefetch-method"},next:{title:"Pagination",permalink:"/docs/4.1/pagination"}},p={},d=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:2},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:2},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:2},{value:"Usage",id:"usage",level:2}],c={toc:d},m="wrapper";function h(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,r.yg)("p",null,"You must start by installing this package:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,r.yg)("h2",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,r.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,r.yg)("h2",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,r.yg)("p",null,"In order to use this, you must first be sure that the ",(0,r.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,r.yg)("h2",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,r.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,r.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")))),(0,r.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,r.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/766e1cc8.2071bfdd.js b/assets/js/766e1cc8.83bf99b5.js similarity index 98% rename from assets/js/766e1cc8.2071bfdd.js rename to assets/js/766e1cc8.83bf99b5.js index 9b468d0751..cd4bc38afa 100644 --- a/assets/js/766e1cc8.2071bfdd.js +++ b/assets/js/766e1cc8.83bf99b5.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7362],{42528:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>r,default:()=>y,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var a=n(58168),o=(n(96540),n(15680));n(67443);const i={id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},r=void 0,l={unversionedId:"input-types",id:"version-4.0/input-types",title:"Input types",description:"Let's admit you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-4.0/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/4.0/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/input-types.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},sidebar:"version-4.0/docs",previous:{title:"External type declaration",permalink:"/docs/4.0/external_type_declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/4.0/inheritance-interfaces"}},u={},p=[{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3}],s={toc:p},c="wrapper";function y(e){let{components:t,...n}=e;return(0,o.yg)(c,(0,a.A)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"Let's admit you are developing an API that returns a list of cities around a location."),(0,o.yg)("p",null,"Your GraphQL query might look like this:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,o.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,o.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,o.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,o.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,o.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,o.yg)("p",null,"In order to declare that type, in GraphQLite, we will declare a ",(0,o.yg)("strong",{parentName:"p"},"Factory"),"."),(0,o.yg)("p",null,"A ",(0,o.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,o.yg)("p",null,"Here is an example of factory:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")),(0,o.yg)("p",null,"and now, you can run query like this:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre"},"mutation {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,o.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,o.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,o.yg)("p",null,"A few important things to notice:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,o.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,o.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,o.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,o.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")),(0,o.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')),(0,o.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,o.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,o.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,o.yg)("p",null,"You can use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,o.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')),(0,o.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,o.yg)("small",null,"Available in GraphQLite 4.0+"),(0,o.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,o.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,o.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,o.yg)("p",null,"Here is an annotated sample:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')),(0,o.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,o.yg)("small",null,"Available in GraphQLite 4.0+"),(0,o.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,o.yg)("p",null,"Image your ",(0,o.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,o.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')),(0,o.yg)("p",null,"With the ",(0,o.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,o.yg)("p",null,"To be able to hide an argument, the argument must have a default value."))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7362],{42528:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>r,default:()=>y,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var a=n(58168),o=(n(96540),n(15680));n(67443);const i={id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},r=void 0,l={unversionedId:"input-types",id:"version-4.0/input-types",title:"Input types",description:"Let's admit you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-4.0/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/4.0/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/input-types.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},sidebar:"version-4.0/docs",previous:{title:"External type declaration",permalink:"/docs/4.0/external_type_declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/4.0/inheritance-interfaces"}},u={},p=[{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3}],s={toc:p},c="wrapper";function y(e){let{components:t,...n}=e;return(0,o.yg)(c,(0,a.A)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"Let's admit you are developing an API that returns a list of cities around a location."),(0,o.yg)("p",null,"Your GraphQL query might look like this:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,o.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,o.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,o.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,o.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,o.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,o.yg)("p",null,"In order to declare that type, in GraphQLite, we will declare a ",(0,o.yg)("strong",{parentName:"p"},"Factory"),"."),(0,o.yg)("p",null,"A ",(0,o.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,o.yg)("p",null,"Here is an example of factory:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")),(0,o.yg)("p",null,"and now, you can run query like this:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre"},"mutation {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,o.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,o.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,o.yg)("p",null,"A few important things to notice:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,o.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,o.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,o.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,o.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")),(0,o.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre"},'/**\n * @Factory(name="MyNewInputName", default=true)\n */\n')),(0,o.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,o.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,o.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,o.yg)("p",null,"You can use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@UseInputType")," annotation to force an input type of a parameter."),(0,o.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @UseInputType(for="$id", inputType="ID!")\n */\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')),(0,o.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,o.yg)("small",null,"Available in GraphQLite 4.0+"),(0,o.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,o.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,o.yg)("inlineCode",{parentName:"p"},"@UseInputType")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation to achieve your goal."),(0,o.yg)("p",null,"Here is an annotated sample:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n * @Factory(name="ProductRefInput", default=true)\n */\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n * @Factory(name="CreateProductInput", default=false)\n */\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n *\n * @Mutation\n * @UseInputType(for="$product", inputType="CreateProductInput!")\n */\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @Query\n * @return Color[]\n */\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')),(0,o.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,o.yg)("small",null,"Available in GraphQLite 4.0+"),(0,o.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,o.yg)("p",null,"Image your ",(0,o.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,o.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Factory()\n * @HideParameter(for="$lazyLoad")\n */\npublic function getProductById(string $id, bool $lazyLoad = true): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n')),(0,o.yg)("p",null,"With the ",(0,o.yg)("inlineCode",{parentName:"p"},"@HideParameter")," annotation, you can choose to remove from the GraphQL schema any argument."),(0,o.yg)("p",null,"To be able to hide an argument, the argument must have a default value."))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/767c28af.9b3f2b00.js b/assets/js/767c28af.7606b775.js similarity index 98% rename from assets/js/767c28af.9b3f2b00.js rename to assets/js/767c28af.7606b775.js index b93b7acbfa..a81f7a5dc0 100644 --- a/assets/js/767c28af.9b3f2b00.js +++ b/assets/js/767c28af.7606b775.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[229],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var r=a(96540),n=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return r.createElement("div",{role:"tabpanel",className:(0,n.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var r=a(58168),n=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function p(e){return function(e){return n.Children.map(e,(e=>{if(!e||(0,n.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:r,default:n}}=e;return{value:t,label:a,attributes:r,default:n}}))}function d(e){const{values:t,children:a}=e;return(0,n.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const r=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,n.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(r.location.search);t.set(l,e),r.replace({...r.location,search:t.toString()})}),[l,r])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:r}=e,l=d(e),[u,o]=(0,n.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:r}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[r,l]=(0,c.Dv)(a);return[r,(0,n.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:r}),b=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,n.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,n.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function y(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),r=i[a].value;r!==o&&(p(t),s(r))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return n.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return n.createElement("li",(0,r.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,l.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:r}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===r));return e?(0,n.cloneElement)(e,{className:"margin-top--md"}):null}return n.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,n.cloneElement)(e,{key:t,hidden:e.props.value!==r}))))}function w(e){const t=h(e);return n.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},n.createElement(y,(0,r.A)({},e,t)),n.createElement(v,(0,r.A)({},e,t)))}function T(e){const t=(0,b.A)();return n.createElement(w,(0,r.A)({key:String(t)},e))}},24432:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var r=a(58168),n=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},s=void 0,i={unversionedId:"features",id:"version-4.1/features",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-4.1/features.mdx",sourceDirName:".",slug:"/",permalink:"/docs/4.1/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/features.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},sidebar:"version-4.1/docs",next:{title:"Getting Started",permalink:"/docs/4.1/getting-started"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:p},m="wrapper";function g(e){let{components:t,...a}=e;return(0,n.yg)(m,(0,r.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,n.yg)("p",{align:"center"},(0,n.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,n.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,n.yg)("h2",{id:"features"},"Features"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,n.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,n.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,n.yg)("h2",{id:"basic-example"},"Basic example"),(0,n.yg)("p",null,"First, declare a query in your controller:"),(0,n.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,n.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,n.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,n.yg)("p",null,"Then, annotate the ",(0,n.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,n.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,n.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,n.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,n.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-grapql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[229],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var r=a(96540),n=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return r.createElement("div",{role:"tabpanel",className:(0,n.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var r=a(58168),n=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function p(e){return function(e){return n.Children.map(e,(e=>{if(!e||(0,n.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:r,default:n}}=e;return{value:t,label:a,attributes:r,default:n}}))}function d(e){const{values:t,children:a}=e;return(0,n.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const r=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,n.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(r.location.search);t.set(l,e),r.replace({...r.location,search:t.toString()})}),[l,r])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:r}=e,l=d(e),[u,o]=(0,n.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:r}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[r,l]=(0,c.Dv)(a);return[r,(0,n.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:r}),b=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,n.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,n.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function y(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),r=i[a].value;r!==o&&(p(t),s(r))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return n.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return n.createElement("li",(0,r.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,l.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:r}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===r));return e?(0,n.cloneElement)(e,{className:"margin-top--md"}):null}return n.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,n.cloneElement)(e,{key:t,hidden:e.props.value!==r}))))}function w(e){const t=h(e);return n.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},n.createElement(y,(0,r.A)({},e,t)),n.createElement(v,(0,r.A)({},e,t)))}function T(e){const t=(0,b.A)();return n.createElement(w,(0,r.A)({key:String(t)},e))}},24432:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var r=a(58168),n=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},s=void 0,i={unversionedId:"features",id:"version-4.1/features",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-4.1/features.mdx",sourceDirName:".",slug:"/",permalink:"/docs/4.1/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/features.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},sidebar:"version-4.1/docs",next:{title:"Getting Started",permalink:"/docs/4.1/getting-started"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:p},m="wrapper";function g(e){let{components:t,...a}=e;return(0,n.yg)(m,(0,r.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,n.yg)("p",{align:"center"},(0,n.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,n.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,n.yg)("h2",{id:"features"},"Features"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,n.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,n.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,n.yg)("h2",{id:"basic-example"},"Basic example"),(0,n.yg)("p",null,"First, declare a query in your controller:"),(0,n.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,n.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,n.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,n.yg)("p",null,"Then, annotate the ",(0,n.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,n.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,n.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,n.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,n.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-grapql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/77cdcd82.17a9de0d.js b/assets/js/77cdcd82.3321530b.js similarity index 89% rename from assets/js/77cdcd82.17a9de0d.js rename to assets/js/77cdcd82.3321530b.js index 0a666d7e3b..dd2d3ac3bb 100644 --- a/assets/js/77cdcd82.17a9de0d.js +++ b/assets/js/77cdcd82.3321530b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3507],{56855:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-6.1/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-6.1/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/6.1/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/troubleshooting.md",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"docs",previous:{title:"Internals",permalink:"/docs/6.1/internals"},next:{title:"Migrating",permalink:"/docs/6.1/migrating"}},l={},u=[],p={toc:u},c="wrapper";function d(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,n.A)({},p,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3507],{56855:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>g,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-6.1/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-6.1/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/6.1/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/troubleshooting.md",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"docs",previous:{title:"Internals",permalink:"/docs/6.1/internals"},next:{title:"Migrating",permalink:"/docs/6.1/migrating"}},l={},u=[],p={toc:u},c="wrapper";function g(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,n.A)({},p,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7810a993.304675e2.js b/assets/js/7810a993.e19c8ecc.js similarity index 99% rename from assets/js/7810a993.304675e2.js rename to assets/js/7810a993.e19c8ecc.js index 68bf2e067a..2defcee53f 100644 --- a/assets/js/7810a993.304675e2.js +++ b/assets/js/7810a993.e19c8ecc.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9271],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},84534:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-7.0.0/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-7.0.0/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/symfony-bundle-advanced.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"docs",previous:{title:"Class with multiple output types",permalink:"/docs/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9271],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},84534:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-7.0.0/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-7.0.0/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/symfony-bundle-advanced.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"docs",previous:{title:"Class with multiple output types",permalink:"/docs/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/78619623.de4f9ce0.js b/assets/js/78619623.acbf2cff.js similarity index 98% rename from assets/js/78619623.de4f9ce0.js rename to assets/js/78619623.acbf2cff.js index 199436f896..aa2c96de2f 100644 --- a/assets/js/78619623.de4f9ce0.js +++ b/assets/js/78619623.acbf2cff.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6209],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},24466:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},u=void 0,s={unversionedId:"extend-input-type",id:"version-4.2/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.2/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/4.2/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/extend-input-type.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"version-4.2/docs",previous:{title:"Custom argument resolving",permalink:"/docs/4.2/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/4.2/multiple-output-types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6209],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},24466:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},u=void 0,s={unversionedId:"extend-input-type",id:"version-4.2/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.2/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/4.2/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/extend-input-type.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"version-4.2/docs",previous:{title:"Custom argument resolving",permalink:"/docs/4.2/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/4.2/multiple-output-types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/78da31a1.d515a8c1.js b/assets/js/78da31a1.cf24aae5.js similarity index 99% rename from assets/js/78da31a1.d515a8c1.js rename to assets/js/78da31a1.cf24aae5.js index b78bdfee6e..c895519f82 100644 --- a/assets/js/78da31a1.d515a8c1.js +++ b/assets/js/78da31a1.cf24aae5.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4053],{19365:(e,n,t)=>{t.d(n,{A:()=>o});var a=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>I});var a=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function h(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??d(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function p(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=h(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!p({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=g({queryString:t,groupId:a}),[d,m]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),y=(()=>{const e=u??d;return p({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!p({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),m(e)}),[s,m,i]),tabValues:i}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(d(n),u(a))},p=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:p,onClick:h},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function N(e){const n=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function I(e){const n=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(n)},e))}},50039:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>g,frontMatter:()=>l,metadata:()=>s,toc:()=>d});var a=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"authentication_authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization",original_id:"authentication_authorization"},u=void 0,s={unversionedId:"authentication_authorization",id:"version-4.1/authentication_authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-4.1/authentication_authorization.mdx",sourceDirName:".",slug:"/authentication_authorization",permalink:"/docs/4.1/authentication_authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/authentication_authorization.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"authentication_authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization",original_id:"authentication_authorization"},sidebar:"version-4.1/docs",previous:{title:"User input validation",permalink:"/docs/4.1/validation"},next:{title:"Fine grained security",permalink:"/docs/4.1/fine-grained-security"}},c={},d=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],h={toc:d},p="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(p,(0,a.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.1/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4053],{19365:(e,n,t)=>{t.d(n,{A:()=>o});var a=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>I});var a=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function h(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??d(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function p(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=h(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!p({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=g({queryString:t,groupId:a}),[d,m]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),y=(()=>{const e=u??d;return p({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!p({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),m(e)}),[s,m,i]),tabValues:i}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(d(n),u(a))},p=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:p,onClick:h},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function N(e){const n=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function I(e){const n=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(n)},e))}},50039:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>g,frontMatter:()=>l,metadata:()=>s,toc:()=>d});var a=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"authentication_authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization",original_id:"authentication_authorization"},u=void 0,s={unversionedId:"authentication_authorization",id:"version-4.1/authentication_authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-4.1/authentication_authorization.mdx",sourceDirName:".",slug:"/authentication_authorization",permalink:"/docs/4.1/authentication_authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/authentication_authorization.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"authentication_authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization",original_id:"authentication_authorization"},sidebar:"version-4.1/docs",previous:{title:"User input validation",permalink:"/docs/4.1/validation"},next:{title:"Fine grained security",permalink:"/docs/4.1/fine-grained-security"}},c={},d=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],h={toc:d},p="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(p,(0,a.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.1/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7afb60b1.736e3057.js b/assets/js/7afb60b1.90db6183.js similarity index 99% rename from assets/js/7afb60b1.736e3057.js rename to assets/js/7afb60b1.90db6183.js index 1678e62bff..ad2d1bde5a 100644 --- a/assets/js/7afb60b1.736e3057.js +++ b/assets/js/7afb60b1.90db6183.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4438],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},9815:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-4.3/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-4.3/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/4.3/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/error-handling.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"version-4.3/docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/4.3/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/4.3/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/4.3/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/4.3/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4438],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},9815:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-4.3/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-4.3/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/4.3/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/error-handling.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"version-4.3/docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/4.3/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/4.3/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/4.3/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/4.3/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7b54f5d5.10cce0ec.js b/assets/js/7b54f5d5.b4c102e0.js similarity index 99% rename from assets/js/7b54f5d5.10cce0ec.js rename to assets/js/7b54f5d5.b4c102e0.js index ce6e26d321..4de81ec1e8 100644 --- a/assets/js/7b54f5d5.10cce0ec.js +++ b/assets/js/7b54f5d5.b4c102e0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4565],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),o=a(57485),p=a(31682),s=a(89466);function d(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function c(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,o.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=c(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[o,p]=m({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=o??d;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),p(e),h(e)}),[p,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:o,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=s.indexOf(t),n=p[a].value;n!==i&&(d(t),o(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},p.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:c},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},71683:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>i,metadata:()=>p,toc:()=>d});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},o=void 0,p={unversionedId:"multiple-output-types",id:"version-5.0/multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-5.0/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/5.0/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/multiple-output-types.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"version-5.0/docs",previous:{title:"Extending an input type",permalink:"/docs/5.0/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/5.0/symfony-bundle-advanced"}},s={},d=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],c={toc:d},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/external-type-declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4565],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),o=a(57485),p=a(31682),s=a(89466);function d(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function c(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,p.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,o.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=c(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[o,p]=m({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=o??d;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),p(e),h(e)}),[p,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:o,tabValues:p}=e;const s=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=s.indexOf(t),n=p[a].value;n!==i&&(d(t),o(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},p.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:c},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},71683:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>i,metadata:()=>p,toc:()=>d});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},o=void 0,p={unversionedId:"multiple-output-types",id:"version-5.0/multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-5.0/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/5.0/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/multiple-output-types.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"version-5.0/docs",previous:{title:"Extending an input type",permalink:"/docs/5.0/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/5.0/symfony-bundle-advanced"}},s={},d=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],c={toc:d},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/5.0/external-type-declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7bf967bc.5b329fb7.js b/assets/js/7bf967bc.ad70e0f4.js similarity index 95% rename from assets/js/7bf967bc.5b329fb7.js rename to assets/js/7bf967bc.ad70e0f4.js index e25d5b8c96..634580cbaa 100644 --- a/assets/js/7bf967bc.5b329fb7.js +++ b/assets/js/7bf967bc.ad70e0f4.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5619],{6064:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>p,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>s});var a=n(58168),l=(n(96540),n(15680));n(67443);const i={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},p=void 0,u={unversionedId:"multiple-output-types",id:"multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/docs/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/next/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/multiple-output-types.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"docs",previous:{title:"Extending an input type",permalink:"/docs/next/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/next/symfony-bundle-advanced"}},o={},s=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],d={toc:s},r="wrapper";function y(e){let{components:t,...n}=e;return(0,l.yg)(r,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n')),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/next/external-type-declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n')),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," attribute and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n")),(0,l.yg)("p",null,"you will write:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n')),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," attribute."))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5619],{6064:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>p,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>s});var a=n(58168),l=(n(96540),n(15680));n(67443);const i={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},p=void 0,u={unversionedId:"multiple-output-types",id:"multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/docs/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/next/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/multiple-output-types.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"docs",previous:{title:"Extending an input type",permalink:"/docs/next/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/next/symfony-bundle-advanced"}},o={},s=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],r={toc:s},d="wrapper";function y(e){let{components:t,...n}=e;return(0,l.yg)(d,(0,a.A)({},r,n,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n')),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/next/external-type-declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n')),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," attribute and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n")),(0,l.yg)("p",null,"you will write:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n')),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," attribute."))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7c27e34c.f1aff3c4.js b/assets/js/7c27e34c.1ac91b53.js similarity index 95% rename from assets/js/7c27e34c.f1aff3c4.js rename to assets/js/7c27e34c.1ac91b53.js index 5ea0bf847d..27578ff1a1 100644 --- a/assets/js/7c27e34c.f1aff3c4.js +++ b/assets/js/7c27e34c.1ac91b53.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9227],{74342:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>u});var r=a(58168),n=(a(96540),a(15680));a(67443);const i={id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},l=void 0,s={unversionedId:"features",id:"version-3.0/features",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-3.0/features.mdx",sourceDirName:".",slug:"/",permalink:"/docs/3.0/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/features.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},sidebar:"version-3.0/docs",next:{title:"Getting Started",permalink:"/docs/3.0/getting-started"}},o={},u=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:u},p="wrapper";function c(e){let{components:t,...a}=e;return(0,n.yg)(p,(0,r.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,n.yg)("p",{align:"center"},(0,n.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,n.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,n.yg)("h2",{id:"features"},"Features"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,n.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony bundle available!"),(0,n.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, extendable types and more!")),(0,n.yg)("h2",{id:"basic-example"},"Basic example"),(0,n.yg)("p",null,"First, declare a query in your controller:"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,n.yg)("p",null,"Then, annotate the ",(0,n.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")),(0,n.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-grapql"},"{\n product(id: 42) {\n name\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9227],{74342:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>u});var r=a(58168),n=(a(96540),a(15680));a(67443);const i={id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},l=void 0,s={unversionedId:"features",id:"version-3.0/features",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-3.0/features.mdx",sourceDirName:".",slug:"/",permalink:"/docs/3.0/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/features.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"features",slug:"/",title:"GraphQLite",sidebar_label:"GraphQLite",original_id:"features"},sidebar:"version-3.0/docs",next:{title:"Getting Started",permalink:"/docs/3.0/getting-started"}},o={},u=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:u},p="wrapper";function c(e){let{components:t,...a}=e;return(0,n.yg)(p,(0,r.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,n.yg)("p",{align:"center"},(0,n.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,n.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,n.yg)("h2",{id:"features"},"Features"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,n.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony bundle available!"),(0,n.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, extendable types and more!")),(0,n.yg)("h2",{id:"basic-example"},"Basic example"),(0,n.yg)("p",null,"First, declare a query in your controller:"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,n.yg)("p",null,"Then, annotate the ",(0,n.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")),(0,n.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,n.yg)("pre",null,(0,n.yg)("code",{parentName:"pre",className:"language-grapql"},"{\n product(id: 42) {\n name\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7e507331.2d8a1ee0.js b/assets/js/7e507331.91373eaa.js similarity index 97% rename from assets/js/7e507331.2d8a1ee0.js rename to assets/js/7e507331.91373eaa.js index a4eab09047..5e5ffb2f1b 100644 --- a/assets/js/7e507331.2d8a1ee0.js +++ b/assets/js/7e507331.91373eaa.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2862],{39987:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-5.0/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/versioned_docs/version-5.0/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/5.0/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/semver.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"version-5.0/docs",previous:{title:"Annotations reference",permalink:"/docs/5.0/annotations-reference"},next:{title:"Changelog",permalink:"/docs/5.0/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2862],{39987:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-5.0/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/versioned_docs/version-5.0/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/5.0/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/semver.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"version-5.0/docs",previous:{title:"Annotations reference",permalink:"/docs/5.0/annotations-reference"},next:{title:"Changelog",permalink:"/docs/5.0/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7e63a40e.32957347.js b/assets/js/7e63a40e.efdb47b0.js similarity index 96% rename from assets/js/7e63a40e.32957347.js rename to assets/js/7e63a40e.efdb47b0.js index 139ada8a2b..fa980d25f3 100644 --- a/assets/js/7e63a40e.32957347.js +++ b/assets/js/7e63a40e.efdb47b0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6984],{40467:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>t,metadata:()=>s,toc:()=>l});var n=i(58168),a=(i(96540),i(15680));i(67443);const t={id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},o=void 0,s={unversionedId:"universal_service_providers",id:"version-4.1/universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-4.1/universal_service_providers.md",sourceDirName:".",slug:"/universal_service_providers",permalink:"/docs/4.1/universal_service_providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/universal_service_providers.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},sidebar:"version-4.1/docs",previous:{title:"Laravel package",permalink:"/docs/4.1/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/4.1/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function h(e){let{components:r,...i}=e;return(0,a.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,a.yg)("p",null,"If your framework is compatible with ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,a.yg)("h2",{id:"installation"},"Installation"),(0,a.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,a.yg)("h2",{id:"requirements"},"Requirements"),(0,a.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,a.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,a.yg)("p",null,"GraphQLite relies on the ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,a.yg)("a",{parentName:"p",href:"/docs/4.1/other-frameworks"},"PSR-15 middleware"),"."),(0,a.yg)("h2",{id:"integration"},"Integration"),(0,a.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,a.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,a.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,a.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"composer.json")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre"},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"index.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6984],{40467:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>t,metadata:()=>s,toc:()=>l});var n=i(58168),a=(i(96540),i(15680));i(67443);const t={id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},o=void 0,s={unversionedId:"universal_service_providers",id:"version-4.1/universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-4.1/universal_service_providers.md",sourceDirName:".",slug:"/universal_service_providers",permalink:"/docs/4.1/universal_service_providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/universal_service_providers.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},sidebar:"version-4.1/docs",previous:{title:"Laravel package",permalink:"/docs/4.1/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/4.1/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function h(e){let{components:r,...i}=e;return(0,a.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,a.yg)("p",null,"If your framework is compatible with ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,a.yg)("h2",{id:"installation"},"Installation"),(0,a.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,a.yg)("h2",{id:"requirements"},"Requirements"),(0,a.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,a.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,a.yg)("p",null,"GraphQLite relies on the ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,a.yg)("a",{parentName:"p",href:"/docs/4.1/other-frameworks"},"PSR-15 middleware"),"."),(0,a.yg)("h2",{id:"integration"},"Integration"),(0,a.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,a.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,a.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,a.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"composer.json")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre"},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"index.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7ee46e43.a30e535b.js b/assets/js/7ee46e43.c3a02fcd.js similarity index 97% rename from assets/js/7ee46e43.a30e535b.js rename to assets/js/7ee46e43.c3a02fcd.js index ee0fd85e4a..d37ea3b346 100644 --- a/assets/js/7ee46e43.a30e535b.js +++ b/assets/js/7ee46e43.c3a02fcd.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2400],{8239:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>p,contentTitle:()=>r,default:()=>y,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},r=void 0,l={unversionedId:"input-types",id:"version-3.0/input-types",title:"Input types",description:"Let's admit you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-3.0/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/3.0/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/input-types.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},sidebar:"version-3.0/docs",previous:{title:"External type declaration",permalink:"/docs/3.0/external_type_declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/3.0/inheritance"}},p={},u=[{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3}],s={toc:u},c="wrapper";function y(t){let{components:e,...n}=t;return(0,i.yg)(c,(0,a.A)({},s,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Let's admit you are developing an API that returns a list of cities around a location."),(0,i.yg)("p",null,"Your GraphQL query might look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,i.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,i.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,i.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,i.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,i.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,i.yg)("p",null,"In order to declare that type, in GraphQLite, we will declare a ",(0,i.yg)("strong",{parentName:"p"},"Factory"),"."),(0,i.yg)("p",null,"A ",(0,i.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,i.yg)("p",null,"Here is an example of factory:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")),(0,i.yg)("p",null,"and now, you can run query like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"mutation {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,i.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,i.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,i.yg)("p",null,"A few important things to notice:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,i.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,i.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,i.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,i.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")),(0,i.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'/**\n * @Factory(name="MyNewInputName")\n */\n')),(0,i.yg)("p",null,"Most of the time, the input type name will be completely transparent to you, so there is no real reason\nto customize it."))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2400],{8239:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>p,contentTitle:()=>r,default:()=>y,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},r=void 0,l={unversionedId:"input-types",id:"version-3.0/input-types",title:"Input types",description:"Let's admit you are developing an API that returns a list of cities around a location.",source:"@site/versioned_docs/version-3.0/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/3.0/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/input-types.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types",original_id:"input-types"},sidebar:"version-3.0/docs",previous:{title:"External type declaration",permalink:"/docs/3.0/external_type_declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/3.0/inheritance"}},p={},u=[{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3}],s={toc:u},c="wrapper";function y(t){let{components:e,...n}=t;return(0,i.yg)(c,(0,a.A)({},s,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Let's admit you are developing an API that returns a list of cities around a location."),(0,i.yg)("p",null,"Your GraphQL query might look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return City[]\n */\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,i.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,i.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,i.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,i.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,i.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,i.yg)("p",null,"In order to declare that type, in GraphQLite, we will declare a ",(0,i.yg)("strong",{parentName:"p"},"Factory"),"."),(0,i.yg)("p",null,"A ",(0,i.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,i.yg)("p",null,"Here is an example of factory:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"class MyFactory\n{\n /**\n * The Factory annotation will create automatically a LocationInput input type in GraphQL.\n *\n * @Factory()\n */\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")),(0,i.yg)("p",null,"and now, you can run query like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"mutation {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,i.yg)("strong",{parentName:"li"},"@Factory")," annotation."),(0,i.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,i.yg)("p",null,"A few important things to notice:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,i.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,i.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,i.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,i.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"/**\n * @Factory()\n */\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")),(0,i.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'/**\n * @Factory(name="MyNewInputName")\n */\n')),(0,i.yg)("p",null,"Most of the time, the input type name will be completely transparent to you, so there is no real reason\nto customize it."))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8158.5d3c0904.js b/assets/js/8158.5d3c0904.js new file mode 100644 index 0000000000..b4dba9d6fe --- /dev/null +++ b/assets/js/8158.5d3c0904.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8158],{48158:(a,e,c)=>{c.r(e),c.d(e,{DocSearchModal:()=>s.a1});var s=c(55600)}}]); \ No newline at end of file diff --git a/assets/js/820db038.84479dbc.js b/assets/js/820db038.30040718.js similarity index 99% rename from assets/js/820db038.84479dbc.js rename to assets/js/820db038.30040718.js index 372ef7661b..c7b9e171f8 100644 --- a/assets/js/820db038.84479dbc.js +++ b/assets/js/820db038.30040718.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9742],{19365:(e,n,a)=>{a.d(n,{A:()=>s});var t=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function s(e){let{children:n,hidden:a,className:s}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,s),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),i=a(20053),s=a(23104),l=a(56347),p=a(57485),o=a(31682),u=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function d(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??c(a);return function(e){const n=(0,o.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function m(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function y(e){let{queryString:n=!1,groupId:a}=e;const t=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(t.location.search);n.set(i,e),t.replace({...t.location,search:n.toString()})}),[i,t])]}function g(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,i=d(e),[s,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:i}))),[p,o]=y({queryString:a,groupId:t}),[c,g]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,i]=(0,u.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:t}),h=(()=>{const e=p??c;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{h&&l(h)}),[h]);return{selectedValue:s,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),o(e),g(e)}),[o,g,i]),tabValues:i}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:p,tabValues:o}=e;const u=[],{blockElementScrollPositionUntilNextRender:c}=(0,s.a_)(),d=e=>{const n=e.currentTarget,a=u.indexOf(n),t=o[a].value;t!==l&&(c(n),p(t))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=u.indexOf(e.currentTarget)+1;n=u[a]??u[0];break}case"ArrowLeft":{const a=u.indexOf(e.currentTarget)-1;n=u[a]??u[u.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},n)},o.map((e=>{let{value:n,label:a,attributes:s}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>u.push(e),onKeyDown:m,onClick:d},s,{className:(0,i.A)("tabs__item",f.tabItem,s?.className,{"tabs__item--active":l===n})}),a??n)})))}function N(e){let{lazy:n,children:a,selectedValue:t}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function v(e){const n=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(N,(0,t.A)({},e,n)))}function T(e){const n=(0,h.A)();return r.createElement(v,(0,t.A)({key:String(n)},e))}},4466:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>o});var t=a(58168),r=(a(96540),a(15680));a(67443),a(11470),a(19365);const i={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},s=void 0,l={unversionedId:"type-mapping",id:"version-6.1/type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-6.1/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/6.1/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/type-mapping.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"docs",previous:{title:"Mutations",permalink:"/docs/6.1/mutations"},next:{title:"Autowiring services",permalink:"/docs/6.1/autowiring"}},p={},o=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Enum types with myclabs/php-enum",id:"enum-types-with-myclabsphp-enum",level:3},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],u={toc:o},c="wrapper";function d(e){let{components:n,...a}=e;return(0,r.yg)(c,(0,t.A)({},u,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"As explained in the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,r.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,r.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"string")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"int")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"bool")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"float"))),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,r.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,r.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,r.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n')),(0,r.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,r.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,r.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,r.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,r.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,r.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')),(0,r.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,r.yg)("p",null,"GraphQL comes with a native ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,r.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,r.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n')),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,r.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,r.yg)("p",null,"You can learn more about forcing output types in the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/custom-types"},"custom types section"),"."),(0,r.yg)("h3",{id:"id-class"},"ID class"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n")),(0,r.yg)("p",null,"Note that you can also use the ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")),(0,r.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,r.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,r.yg)("p",null,"When used as an output type, ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,r.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,r.yg)("div",{class:"alert alert--danger"},"PHP ",(0,r.yg)("code",null,"DateTime")," type is not supported."),(0,r.yg)("h2",{id:"union-types"},"Union types"),(0,r.yg)("p",null,"You can create a GraphQL union type ",(0,r.yg)("em",{parentName:"p"},"on the fly")," using the pipe ",(0,r.yg)("inlineCode",{parentName:"p"},"|")," operator in the PHPDoc:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Company|Contact <== can return a company OR a contact.\n */\n#[Query]\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")),(0,r.yg)("h2",{id:"enum-types"},"Enum types"),(0,r.yg)("p",null,"PHP 8.1 introduced native support for Enums. GraphQLite now also supports native enums as of version 5.1."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nenum Status: string\n{\n case ON = 'on';\n case OFF = 'off';\n case PENDING = 'pending';\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return User[]\n */\n#[Query]\npublic function users(Status $status): array\n{\n if ($status === Status::ON) {\n // Your logic\n }\n // ...\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: Status!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,r.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," property on the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace Model\\User;\n\n#[Type(name: "UserStatus")]\nenum Status: string\n{\n // ...\n}\n')),(0,r.yg)("h3",{id:"enum-types-with-myclabsphp-enum"},"Enum types with myclabs/php-enum"),(0,r.yg)("div",{class:"alert alert--danger"},"This implementation is now deprecated and will be removed in the future. You are advised to use native enums instead."),(0,r.yg)("p",null,(0,r.yg)("em",{parentName:"p"},"Prior to version 5.1, GraphQLite only supported Enums through the 3rd party library, ",(0,r.yg)("a",{parentName:"em",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),". If you'd like to use this implementation you'll first need to add this library as a dependency to your application.")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,r.yg)("p",null,"Now, any class extending the ",(0,r.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,r.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,r.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,r.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,r.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation. Note that a description (reason) is required for the annotation to be rendered."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n * @deprecated use field `name` instead\n */\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,r.yg)("p",null,"This will add the ",(0,r.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,r.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,r.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,r.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,r.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,r.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,r.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,r.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,r.yg)("p",null,"If you need more types, you can check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,r.yg)("p",null,"Or if you have some special needs, ",(0,r.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9742],{19365:(e,n,a)=>{a.d(n,{A:()=>s});var t=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function s(e){let{children:n,hidden:a,className:s}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,s),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),i=a(20053),s=a(23104),l=a(56347),p=a(57485),o=a(31682),u=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function d(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??c(a);return function(e){const n=(0,o.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function m(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function y(e){let{queryString:n=!1,groupId:a}=e;const t=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(t.location.search);n.set(i,e),t.replace({...t.location,search:n.toString()})}),[i,t])]}function g(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,i=d(e),[s,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:i}))),[p,o]=y({queryString:a,groupId:t}),[c,g]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,i]=(0,u.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:t}),h=(()=>{const e=p??c;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{h&&l(h)}),[h]);return{selectedValue:s,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),o(e),g(e)}),[o,g,i]),tabValues:i}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:p,tabValues:o}=e;const u=[],{blockElementScrollPositionUntilNextRender:c}=(0,s.a_)(),d=e=>{const n=e.currentTarget,a=u.indexOf(n),t=o[a].value;t!==l&&(c(n),p(t))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=u.indexOf(e.currentTarget)+1;n=u[a]??u[0];break}case"ArrowLeft":{const a=u.indexOf(e.currentTarget)-1;n=u[a]??u[u.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},n)},o.map((e=>{let{value:n,label:a,attributes:s}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>u.push(e),onKeyDown:m,onClick:d},s,{className:(0,i.A)("tabs__item",f.tabItem,s?.className,{"tabs__item--active":l===n})}),a??n)})))}function N(e){let{lazy:n,children:a,selectedValue:t}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function v(e){const n=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(N,(0,t.A)({},e,n)))}function T(e){const n=(0,h.A)();return r.createElement(v,(0,t.A)({key:String(n)},e))}},4466:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>o});var t=a(58168),r=(a(96540),a(15680));a(67443),a(11470),a(19365);const i={id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},s=void 0,l={unversionedId:"type-mapping",id:"version-6.1/type-mapping",title:"Type mapping",description:"As explained in the queries section, the job of GraphQLite is to create GraphQL types from PHP types.",source:"@site/versioned_docs/version-6.1/type-mapping.mdx",sourceDirName:".",slug:"/type-mapping",permalink:"/docs/6.1/type-mapping",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/type-mapping.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"type-mapping",title:"Type mapping",sidebar_label:"Type mapping"},sidebar:"docs",previous:{title:"Mutations",permalink:"/docs/6.1/mutations"},next:{title:"Autowiring services",permalink:"/docs/6.1/autowiring"}},p={},o=[{value:"Scalar mapping",id:"scalar-mapping",level:2},{value:"Class mapping",id:"class-mapping",level:2},{value:"Array mapping",id:"array-mapping",level:2},{value:"ID mapping",id:"id-mapping",level:2},{value:"Force the outputType",id:"force-the-outputtype",level:3},{value:"ID class",id:"id-class",level:3},{value:"Date mapping",id:"date-mapping",level:2},{value:"Union types",id:"union-types",level:2},{value:"Enum types",id:"enum-types",level:2},{value:"Enum types with myclabs/php-enum",id:"enum-types-with-myclabsphp-enum",level:3},{value:"Deprecation of fields",id:"deprecation-of-fields",level:2},{value:"More scalar types",id:"more-scalar-types",level:2}],u={toc:o},c="wrapper";function d(e){let{components:n,...a}=e;return(0,r.yg)(c,(0,t.A)({},u,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"As explained in the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/queries"},"queries")," section, the job of GraphQLite is to create GraphQL types from PHP types."),(0,r.yg)("h2",{id:"scalar-mapping"},"Scalar mapping"),(0,r.yg)("p",null,"Scalar PHP types can be type-hinted to the corresponding GraphQL types:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"string")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"int")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"bool")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"float"))),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,r.yg)("h2",{id:"class-mapping"},"Class mapping"),(0,r.yg)("p",null,"When returning a PHP class in a query, you must annotate this class using ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotations:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Note:")," The GraphQL output type name generated by GraphQLite is equal to the class name of the PHP class. So if your\nPHP class is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entities\\Product"),', then the GraphQL type will be named "Product".'),(0,r.yg)("p",null,'In case you have several types with the same class name in different namespaces, you will face a naming collision.\nHopefully, you can force the name of the GraphQL output type using the "name" attribute:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(name: "MyProduct")]\nclass Product { /* ... */ }\n')),(0,r.yg)("div",{class:"alert alert--info"},"You can also put a ",(0,r.yg)("a",{href:"inheritance-interfaces#mapping-interfaces"},(0,r.yg)("code",null,"@Type")," annotation on a PHP interface to map your code to a GraphQL interface"),"."),(0,r.yg)("h2",{id:"array-mapping"},"Array mapping"),(0,r.yg)("p",null,"You can type-hint against arrays (or iterators) as long as you add a detailed ",(0,r.yg)("inlineCode",{parentName:"p"},"@return")," statement in the PHPDoc."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[] <=== we specify that the array is an array of User objects.\n */\n#[Query]\npublic function users(int $limit, int $offset): array\n{\n // Some code that returns an array of "users".\n}\n')),(0,r.yg)("h2",{id:"id-mapping"},"ID mapping"),(0,r.yg)("p",null,"GraphQL comes with a native ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," type. PHP has no such type."),(0,r.yg)("p",null,"There are two ways with GraphQLite to handle such type."),(0,r.yg)("h3",{id:"force-the-outputtype"},"Force the outputType"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Field(outputType: "ID")]\npublic function getId(): string\n{\n // ...\n}\n')),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute of the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, you can force the output type to ",(0,r.yg)("inlineCode",{parentName:"p"},"ID"),"."),(0,r.yg)("p",null,"You can learn more about forcing output types in the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.1/custom-types"},"custom types section"),"."),(0,r.yg)("h3",{id:"id-class"},"ID class"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Field]\npublic function getId(): ID\n{\n // ...\n}\n")),(0,r.yg)("p",null,"Note that you can also use the ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," class as an input type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Types\\ID;\n\n#[Mutation]\npublic function save(ID $id, string $name): Product\n{\n // ...\n}\n")),(0,r.yg)("h2",{id:"date-mapping"},"Date mapping"),(0,r.yg)("p",null,"Out of the box, GraphQL does not have a ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTime")," type, but we took the liberty to add one, with sensible defaults."),(0,r.yg)("p",null,"When used as an output type, ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTimeImmutable")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTimeInterface")," PHP classes are\nautomatically mapped to this ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTime")," GraphQL type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getDate(): \\DateTimeInterface\n{\n return $this->date;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"date")," field will be of type ",(0,r.yg)("inlineCode",{parentName:"p"},"DateTime"),". In the returned JSON response to a query, the date is formatted as a string\nin the ",(0,r.yg)("strong",{parentName:"p"},"ISO8601")," format (aka ATOM format)."),(0,r.yg)("div",{class:"alert alert--danger"},"PHP ",(0,r.yg)("code",null,"DateTime")," type is not supported."),(0,r.yg)("h2",{id:"union-types"},"Union types"),(0,r.yg)("p",null,"You can create a GraphQL union type ",(0,r.yg)("em",{parentName:"p"},"on the fly")," using the pipe ",(0,r.yg)("inlineCode",{parentName:"p"},"|")," operator in the PHPDoc:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Company|Contact <== can return a company OR a contact.\n */\n#[Query]\npublic function companyOrContact(int $id)\n{\n // Some code that returns a company or a contact.\n}\n")),(0,r.yg)("h2",{id:"enum-types"},"Enum types"),(0,r.yg)("p",null,"PHP 8.1 introduced native support for Enums. GraphQLite now also supports native enums as of version 5.1."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nenum Status: string\n{\n case ON = 'on';\n case OFF = 'off';\n case PENDING = 'pending';\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return User[]\n */\n#[Query]\npublic function users(Status $status): array\n{\n if ($status === Status::ON) {\n // Your logic\n }\n // ...\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: Status!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,r.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," property on the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace Model\\User;\n\n#[Type(name: "UserStatus")]\nenum Status: string\n{\n // ...\n}\n')),(0,r.yg)("h3",{id:"enum-types-with-myclabsphp-enum"},"Enum types with myclabs/php-enum"),(0,r.yg)("div",{class:"alert alert--danger"},"This implementation is now deprecated and will be removed in the future. You are advised to use native enums instead."),(0,r.yg)("p",null,(0,r.yg)("em",{parentName:"p"},"Prior to version 5.1, GraphQLite only supported Enums through the 3rd party library, ",(0,r.yg)("a",{parentName:"em",href:"https://github.com/myclabs/php-enum"},"myclabs/php-enum"),". If you'd like to use this implementation you'll first need to add this library as a dependency to your application.")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require myclabs/php-enum\n")),(0,r.yg)("p",null,"Now, any class extending the ",(0,r.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," class will be mapped to a GraphQL enum:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use MyCLabs\\Enum\\Enum;\n\nclass StatusEnum extends Enum\n{\n private const ON = 'on';\n private const OFF = 'off';\n private const PENDING = 'pending';\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @return User[]\n */\n#[Query]\npublic function users(StatusEnum $status): array\n{\n if ($status == StatusEnum::ON()) {\n // Note that the "magic" ON() method returns an instance of the StatusEnum class.\n // Also, note that we are comparing this instance using "==" (using "===" would fail as we have 2 different instances here)\n // ...\n }\n // ...\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"query users($status: StatusEnum!) {}\n users(status: $status) {\n id\n }\n}\n")),(0,r.yg)("p",null,"By default, the name of the GraphQL enum type will be the name of the class. If you have a naming conflict (two classes\nthat live in different namespaces with the same class name), you can solve it using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@EnumType")," annotation:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\EnumType;\n\n#[EnumType(name: "UserStatus")]\nclass StatusEnum extends Enum\n{\n // ...\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},'GraphQLite must be able to find all the classes extending the "MyCLabs\\Enum" class in your project. By default, GraphQLite will look for "Enum" classes in the namespaces declared for the types. For this reason, ',(0,r.yg)("strong",null,"your enum classes MUST be in one of the namespaces declared for the types in your GraphQLite configuration file.")),(0,r.yg)("h2",{id:"deprecation-of-fields"},"Deprecation of fields"),(0,r.yg)("p",null,"You can mark a field as deprecated in your GraphQL Schema by just annotating it with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@deprecated")," PHPDoc annotation. Note that a description (reason) is required for the annotation to be rendered."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n * @deprecated use field `name` instead\n */\n public function getProductName(): string\n {\n return $this->name;\n }\n}\n")),(0,r.yg)("p",null,"This will add the ",(0,r.yg)("inlineCode",{parentName:"p"},"@deprecated")," directive to the field in the GraphQL Schema which sets the ",(0,r.yg)("inlineCode",{parentName:"p"},"isDeprecated")," field to ",(0,r.yg)("inlineCode",{parentName:"p"},"true")," and adds the reason to the ",(0,r.yg)("inlineCode",{parentName:"p"},"deprecationReason")," field in an introspection query. Fields marked as deprecated can still be queried, but will be returned in an introspection query only if ",(0,r.yg)("inlineCode",{parentName:"p"},"includeDeprecated")," is set to ",(0,r.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},'query {\n __type(name: "Product") {\n\ufffc fields(includeDeprecated: true) {\n\ufffc name\n\ufffc isDeprecated\n\ufffc deprecationReason\n\ufffc }\n\ufffc }\n}\n')),(0,r.yg)("h2",{id:"more-scalar-types"},"More scalar types"),(0,r.yg)("p",null,'GraphQL supports "custom" scalar types. GraphQLite supports adding more GraphQL scalar types.'),(0,r.yg)("p",null,"If you need more types, you can check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),".\nIt adds support for more scalar types out of the box in GraphQLite."),(0,r.yg)("p",null,"Or if you have some special needs, ",(0,r.yg)("a",{parentName:"p",href:"custom-types#registering-a-custom-scalar-type-advanced"},"you can develop your own scalar types"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/822cd419.ae42397e.js b/assets/js/822cd419.6ccd8cfe.js similarity index 92% rename from assets/js/822cd419.ae42397e.js rename to assets/js/822cd419.6ccd8cfe.js index bc0181d6bc..0656b9a535 100644 --- a/assets/js/822cd419.ae42397e.js +++ b/assets/js/822cd419.6ccd8cfe.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7382],{87445:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-6.1/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-6.1/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/6.1/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/universal-service-providers.md",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"docs",previous:{title:"Laravel package",permalink:"/docs/6.1/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/6.1/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/6.1/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7382],{87445:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>p});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-6.1/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-6.1/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/6.1/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/universal-service-providers.md",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"docs",previous:{title:"Laravel package",permalink:"/docs/6.1/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/6.1/other-frameworks"}},l={},p=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:p},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/6.1/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/82395e72.dc7ee846.js b/assets/js/82395e72.4e7d40c0.js similarity index 99% rename from assets/js/82395e72.dc7ee846.js rename to assets/js/82395e72.4e7d40c0.js index fbd05b1cf7..5d930e0c10 100644 --- a/assets/js/82395e72.dc7ee846.js +++ b/assets/js/82395e72.4e7d40c0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3576],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>V});var n=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),d=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function c(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??p(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function m(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:t}=e;const n=(0,l.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function y(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!m({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=h({queryString:t,groupId:n}),[p,y]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,d.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),g=(()=>{const e=s??p;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,i]),tabValues:i}}var g=t(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:a,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),c=e=>{const a=e.currentTarget,t=d.indexOf(a),n=u[t].value;n!==l&&(p(a),s(n))},m=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const t=d.indexOf(e.currentTarget)+1;a=d[t]??d[0];break}case"ArrowLeft":{const t=d.indexOf(e.currentTarget)-1;a=d[t]??d[d.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===a?0:-1,"aria-selected":l===a,key:a,ref:e=>d.push(e),onKeyDown:m,onClick:c},o,{className:(0,i.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=y(e);return r.createElement("div",{className:(0,i.A)("tabs-container",v.tabList)},r.createElement(f,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function V(e){const a=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},39698:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-4.3/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-4.3/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/4.3/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/validation.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"version-4.3/docs",previous:{title:"Error handling",permalink:"/docs/4.3/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/4.3/authentication-authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],c={toc:p},m="wrapper";function h(e){let{components:a,...t}=e;return(0,r.yg)(m,(0,n.A)({},c,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,r.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,r.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,r.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,r.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,r.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,r.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,r.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,r.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,r.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,r.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,r.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,r.yg)("p",null,"If the data entered by the user is ",(0,r.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,r.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\Graphqlite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,r.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,r.yg)("p",null,"You can also pass an array to the ",(0,r.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,r.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3576],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>V});var n=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),d=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function c(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??p(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function m(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:t}=e;const n=(0,l.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function y(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!m({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=h({queryString:t,groupId:n}),[p,y]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,d.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),g=(()=>{const e=s??p;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,i]),tabValues:i}}var g=t(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:a,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),c=e=>{const a=e.currentTarget,t=d.indexOf(a),n=u[t].value;n!==l&&(p(a),s(n))},m=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const t=d.indexOf(e.currentTarget)+1;a=d[t]??d[0];break}case"ArrowLeft":{const t=d.indexOf(e.currentTarget)-1;a=d[t]??d[d.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===a?0:-1,"aria-selected":l===a,key:a,ref:e=>d.push(e),onKeyDown:m,onClick:c},o,{className:(0,i.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=y(e);return r.createElement("div",{className:(0,i.A)("tabs-container",v.tabList)},r.createElement(f,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function V(e){const a=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},39698:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-4.3/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-4.3/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/4.3/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/validation.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"version-4.3/docs",previous:{title:"Error handling",permalink:"/docs/4.3/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/4.3/authentication-authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],c={toc:p},m="wrapper";function h(e){let{components:a,...t}=e;return(0,r.yg)(m,(0,n.A)({},c,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,r.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,r.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,r.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,r.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,r.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,r.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,r.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,r.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,r.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,r.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,r.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,r.yg)("p",null,"If the data entered by the user is ",(0,r.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,r.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\Graphqlite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,r.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,r.yg)("p",null,"You can also pass an array to the ",(0,r.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,r.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8299d165.ebbcc1be.js b/assets/js/8299d165.b0baee98.js similarity index 93% rename from assets/js/8299d165.ebbcc1be.js rename to assets/js/8299d165.b0baee98.js index 1b58a48590..5e185c06e0 100644 --- a/assets/js/8299d165.ebbcc1be.js +++ b/assets/js/8299d165.b0baee98.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6009],{19365:(e,a,n)=>{n.d(a,{A:()=>i});var t=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:n,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},a)}},11470:(e,a,n)=>{n.d(a,{A:()=>P});var t=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:n,attributes:t,default:r}}=e;return{value:a,label:n,attributes:t,default:r}}))}function c(e){const{values:a,children:n}=e;return(0,r.useMemo)((()=>{const e=a??d(n);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,n])}function g(e){let{value:a,tabValues:n}=e;return n.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:n}=e;const t=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:n}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:a,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(t.location.search);a.set(l,e),t.replace({...t.location,search:a.toString()})}),[l,t])]}function m(e){const{defaultValue:a,queryString:n=!1,groupId:t}=e,l=c(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const t=n.find((e=>e.default))??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:n,groupId:t}),[d,m]=function(e){let{groupId:a}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(a),[t,l]=(0,p.Dv)(n);return[t,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:t}),y=(()=>{const e=s??d;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:d}=(0,i.a_)(),c=e=>{const a=e.currentTarget,n=p.indexOf(a),t=u[n].value;t!==o&&(d(a),s(t))},g=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;a=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;a=p[n]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},a)},u.map((e=>{let{value:a,label:n,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:c},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),n??a)})))}function b(e){let{lazy:a,children:n,selectedValue:t}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==t}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,t.A)({},e,a)),r.createElement(b,(0,t.A)({},e,a)))}function P(e){const a=(0,y.A)();return r.createElement(w,(0,t.A)({key:String(a)},e))}},27736:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>d});var t=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-4.3/laravel-package-advanced",title:"Laravel package: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.3/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/4.3/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/laravel-package-advanced.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},sidebar:"version-4.3/docs",previous:{title:"Symfony specific features",permalink:"/docs/4.3/symfony-bundle-advanced"},next:{title:"Internals",permalink:"/docs/4.3/internals"}},p={},d=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3}],c={toc:d},g="wrapper";function h(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},c,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n'))))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6009],{19365:(e,a,n)=>{n.d(a,{A:()=>i});var t=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:n,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},a)}},11470:(e,a,n)=>{n.d(a,{A:()=>P});var t=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:n,attributes:t,default:r}}=e;return{value:a,label:n,attributes:t,default:r}}))}function d(e){const{values:a,children:n}=e;return(0,r.useMemo)((()=>{const e=a??c(n);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,n])}function g(e){let{value:a,tabValues:n}=e;return n.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:n}=e;const t=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:n}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:a,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(t.location.search);a.set(l,e),t.replace({...t.location,search:a.toString()})}),[l,t])]}function m(e){const{defaultValue:a,queryString:n=!1,groupId:t}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const t=n.find((e=>e.default))??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:n,groupId:t}),[c,m]=function(e){let{groupId:a}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(a),[t,l]=(0,p.Dv)(n);return[t,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:t}),y=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const a=e.currentTarget,n=p.indexOf(a),t=u[n].value;t!==o&&(c(a),s(t))},g=e=>{let a=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;a=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;a=p[n]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},a)},u.map((e=>{let{value:a,label:n,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),n??a)})))}function b(e){let{lazy:a,children:n,selectedValue:t}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==t}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,t.A)({},e,a)),r.createElement(b,(0,t.A)({},e,a)))}function P(e){const a=(0,y.A)();return r.createElement(w,(0,t.A)({key:String(a)},e))}},27736:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var t=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-4.3/laravel-package-advanced",title:"Laravel package: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-4.3/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/4.3/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/laravel-package-advanced.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},sidebar:"version-4.3/docs",previous:{title:"Symfony specific features",permalink:"/docs/4.3/symfony-bundle-advanced"},next:{title:"Internals",permalink:"/docs/4.3/internals"}},p={},c=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3}],d={toc:c},g="wrapper";function h(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n'))))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/843ebfb4.2d883124.js b/assets/js/843ebfb4.4915cff2.js similarity index 98% rename from assets/js/843ebfb4.2d883124.js rename to assets/js/843ebfb4.4915cff2.js index 4d9d239bba..11eb762ecc 100644 --- a/assets/js/843ebfb4.2d883124.js +++ b/assets/js/843ebfb4.4915cff2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1585],{50237:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>y,frontMatter:()=>o,metadata:()=>l,toc:()=>d});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"validation",title:"Validation",sidebar_label:"User input validation"},r=void 0,l={unversionedId:"validation",id:"validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/docs/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/next/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/validation.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"docs",previous:{title:"Error handling",permalink:"/docs/next/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/next/authentication-authorization"}},s={},d=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / subscription / factory ...",id:"using-the-validator-directly-on-a-query--mutation--subscription--factory-",level:3},{value:"Custom InputType Validation",id:"custom-inputtype-validation",level:2}],p={toc:d},u="wrapper";function y(e){let{components:t,...a}=e;return(0,i.yg)(u,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,i.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,i.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,i.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,i.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,i.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,i.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,i.yg)("pre",{parentName:"li"},(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,i.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,i.yg)("p",null,"Usually, when you use the Symfony validator component, you put attributes in your entities and you validate those entities\nusing the ",(0,i.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")),(0,i.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')),(0,i.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email"\n }\n }\n ]\n}\n')),(0,i.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--subscription--factory-"},"Using the validator directly on a query / mutation / subscription / factory ..."),(0,i.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,i.yg)("p",null,"If the data entered by the user is ",(0,i.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,i.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Assertion]")," attribute to validate directly the user input."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\GraphQLite\\Validator\\Annotations\\Assertion;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\n#[Query]\n#[Assertion(for: "email", constraint: new Assert\\Email())]\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,i.yg)("p",null,'Notice that the "constraint" parameter contains an attribute (it is an attribute wrapped in an attribute).'),(0,i.yg)("p",null,"You can also pass an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Assertion(for: "email", constraint: [new Assert\\NotBlack(), new Assert\\Email()])]\n')),(0,i.yg)("h2",{id:"custom-inputtype-validation"},"Custom InputType Validation"),(0,i.yg)("p",null,"GraphQLite also supports a fully custom validation implementation for all input types defined with an ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute. This offers a way to validate input types before they're available as a method parameter of your query and mutation controllers. This way, when you're using your query or mutation controllers, you can feel confident that your input type objects have already been validated."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("p",null,"It's important to note that this validation implementation does not validate input types created with a factory. If you are creating an input type with a factory, or using primitive parameters in your query/mutation controllers, you should be sure to validate these independently. This is strictly for input type objects."),(0,i.yg)("p",null,"You can use one of the framework validation libraries listed above or implement your own validation for these cases. If you're using input type objects for most all of your query and mutation controllers, then there is little additional validation concerns with regards to user input. There are many reasons why you should consider defaulting to an InputType object, as opposed to individual arguments, for your queries and mutations. This is just one additional perk.")),(0,i.yg)("p",null,"To get started with validation on input types defined by an ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute, you'll first need to register your validator with the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$factory = new SchemaFactory($cache, $this->container);\n$factory->addControllerNamespace('App\\\\Controllers');\n$factory->addTypeNamespace('App');\n// Register your validator\n$factory->setInputTypeValidator($this->container->get('your_validator'));\n$factory->createSchema();\n")),(0,i.yg)("p",null,"Your input type validator must implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Types\\InputTypeValidatorInterface"),", as shown below:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"interface InputTypeValidatorInterface\n{\n /**\n * Checks to see if the Validator is currently enabled.\n */\n public function isEnabled(): bool;\n\n /**\n * Performs the validation of the InputType.\n *\n * @param object $input The input type object to validate\n */\n public function validate(object $input): void;\n}\n")),(0,i.yg)("p",null,"The interface is quite simple. Handle all of your own validation logic in the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method. For example, you might use Symfony's attribute based validation in addition to some other custom validation logic. It's really up to you on how you wish to handle your own validation. The ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method will receive the input type object populated with the user input."),(0,i.yg)("p",null,"You'll notice that the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method has a ",(0,i.yg)("inlineCode",{parentName:"p"},"void")," return. The purpose here is to encourage you to throw an Exception or handle validation output however you best see fit. GraphQLite does it's best to stay out of your way and doesn't make attempts to handle validation output. You can, however, throw an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")," as usual (see ",(0,i.yg)("a",{parentName:"p",href:"error-handling"},"Error Handling")," for more details)."),(0,i.yg)("p",null,"Also available is the ",(0,i.yg)("inlineCode",{parentName:"p"},"isEnabled")," method. This method is checked before executing validation on an InputType being resolved. You can work out your own logic to selectively enable or disable validation through this method. In most cases, you can simply return ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," to keep it always enabled."),(0,i.yg)("p",null,"And that's it, now, anytime an input type is resolved, the validator will be executed on that input type immediately after it has been hydrated with user input."))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1585],{50237:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>y,frontMatter:()=>o,metadata:()=>l,toc:()=>d});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"validation",title:"Validation",sidebar_label:"User input validation"},r=void 0,l={unversionedId:"validation",id:"validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/docs/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/next/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/validation.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"docs",previous:{title:"Error handling",permalink:"/docs/next/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/next/authentication-authorization"}},s={},d=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / subscription / factory ...",id:"using-the-validator-directly-on-a-query--mutation--subscription--factory-",level:3},{value:"Custom InputType Validation",id:"custom-inputtype-validation",level:2}],p={toc:d},u="wrapper";function y(e){let{components:t,...a}=e;return(0,i.yg)(u,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,i.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,i.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,i.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,i.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,i.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,i.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,i.yg)("pre",{parentName:"li"},(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,i.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,i.yg)("p",null,"Usually, when you use the Symfony validator component, you put attributes in your entities and you validate those entities\nusing the ",(0,i.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")),(0,i.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')),(0,i.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email"\n }\n }\n ]\n}\n')),(0,i.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--subscription--factory-"},"Using the validator directly on a query / mutation / subscription / factory ..."),(0,i.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,i.yg)("p",null,"If the data entered by the user is ",(0,i.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,i.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Assertion]")," attribute to validate directly the user input."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\GraphQLite\\Validator\\Annotations\\Assertion;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\n#[Query]\n#[Assertion(for: "email", constraint: new Assert\\Email())]\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,i.yg)("p",null,'Notice that the "constraint" parameter contains an attribute (it is an attribute wrapped in an attribute).'),(0,i.yg)("p",null,"You can also pass an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Assertion(for: "email", constraint: [new Assert\\NotBlack(), new Assert\\Email()])]\n')),(0,i.yg)("h2",{id:"custom-inputtype-validation"},"Custom InputType Validation"),(0,i.yg)("p",null,"GraphQLite also supports a fully custom validation implementation for all input types defined with an ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute. This offers a way to validate input types before they're available as a method parameter of your query and mutation controllers. This way, when you're using your query or mutation controllers, you can feel confident that your input type objects have already been validated."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("p",null,"It's important to note that this validation implementation does not validate input types created with a factory. If you are creating an input type with a factory, or using primitive parameters in your query/mutation controllers, you should be sure to validate these independently. This is strictly for input type objects."),(0,i.yg)("p",null,"You can use one of the framework validation libraries listed above or implement your own validation for these cases. If you're using input type objects for most all of your query and mutation controllers, then there is little additional validation concerns with regards to user input. There are many reasons why you should consider defaulting to an InputType object, as opposed to individual arguments, for your queries and mutations. This is just one additional perk.")),(0,i.yg)("p",null,"To get started with validation on input types defined by an ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute, you'll first need to register your validator with the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$factory = new SchemaFactory($cache, $this->container);\n$factory->addControllerNamespace('App\\\\Controllers');\n$factory->addTypeNamespace('App');\n// Register your validator\n$factory->setInputTypeValidator($this->container->get('your_validator'));\n$factory->createSchema();\n")),(0,i.yg)("p",null,"Your input type validator must implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Types\\InputTypeValidatorInterface"),", as shown below:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"interface InputTypeValidatorInterface\n{\n /**\n * Checks to see if the Validator is currently enabled.\n */\n public function isEnabled(): bool;\n\n /**\n * Performs the validation of the InputType.\n *\n * @param object $input The input type object to validate\n */\n public function validate(object $input): void;\n}\n")),(0,i.yg)("p",null,"The interface is quite simple. Handle all of your own validation logic in the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method. For example, you might use Symfony's attribute based validation in addition to some other custom validation logic. It's really up to you on how you wish to handle your own validation. The ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method will receive the input type object populated with the user input."),(0,i.yg)("p",null,"You'll notice that the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method has a ",(0,i.yg)("inlineCode",{parentName:"p"},"void")," return. The purpose here is to encourage you to throw an Exception or handle validation output however you best see fit. GraphQLite does it's best to stay out of your way and doesn't make attempts to handle validation output. You can, however, throw an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")," as usual (see ",(0,i.yg)("a",{parentName:"p",href:"error-handling"},"Error Handling")," for more details)."),(0,i.yg)("p",null,"Also available is the ",(0,i.yg)("inlineCode",{parentName:"p"},"isEnabled")," method. This method is checked before executing validation on an InputType being resolved. You can work out your own logic to selectively enable or disable validation through this method. In most cases, you can simply return ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," to keep it always enabled."),(0,i.yg)("p",null,"And that's it, now, anytime an input type is resolved, the validator will be executed on that input type immediately after it has been hydrated with user input."))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/85339969.50230dc8.js b/assets/js/85339969.da377a85.js similarity index 98% rename from assets/js/85339969.50230dc8.js rename to assets/js/85339969.da377a85.js index 3d3ee3941e..140414feaf 100644 --- a/assets/js/85339969.50230dc8.js +++ b/assets/js/85339969.da377a85.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7141],{92617:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating"},r=void 0,l={unversionedId:"migrating",id:"version-5.0/migrating",title:"Migrating",description:"Migrating from v4.0 to v4.1",source:"@site/versioned_docs/version-5.0/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/5.0/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/migrating.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating"},sidebar:"version-5.0/docs",previous:{title:"Troubleshooting",permalink:"/docs/5.0/troubleshooting"},next:{title:"Annotations VS Attributes",permalink:"/docs/5.0/doctrine-annotations-attributes"}},s={},g=[{value:"Migrating from v4.0 to v4.1",id:"migrating-from-v40-to-v41",level:2},{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},p="wrapper";function u(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v40-to-v41"},"Migrating from v4.0 to v4.1"),(0,i.yg)("p",null,"GraphQLite follows Semantic Versioning. GraphQLite 4.1 is backward compatible with GraphQLite 4.0. See\n",(0,i.yg)("a",{parentName:"p",href:"/docs/5.0/semver"},"semantic versioning")," for more details."),(0,i.yg)("p",null,"There is one exception though: the ",(0,i.yg)("strong",{parentName:"p"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL\ninput types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"p"},"composer.json")," by running this command:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/annotations-reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7141],{92617:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating"},r=void 0,l={unversionedId:"migrating",id:"version-5.0/migrating",title:"Migrating",description:"Migrating from v4.0 to v4.1",source:"@site/versioned_docs/version-5.0/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/5.0/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/migrating.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating"},sidebar:"version-5.0/docs",previous:{title:"Troubleshooting",permalink:"/docs/5.0/troubleshooting"},next:{title:"Annotations VS Attributes",permalink:"/docs/5.0/doctrine-annotations-attributes"}},s={},g=[{value:"Migrating from v4.0 to v4.1",id:"migrating-from-v40-to-v41",level:2},{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},p="wrapper";function u(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v40-to-v41"},"Migrating from v4.0 to v4.1"),(0,i.yg)("p",null,"GraphQLite follows Semantic Versioning. GraphQLite 4.1 is backward compatible with GraphQLite 4.0. See\n",(0,i.yg)("a",{parentName:"p",href:"/docs/5.0/semver"},"semantic versioning")," for more details."),(0,i.yg)("p",null,"There is one exception though: the ",(0,i.yg)("strong",{parentName:"p"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL\ninput types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"p"},"composer.json")," by running this command:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/annotations-reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/859fcda7.dd618dad.js b/assets/js/859fcda7.e1a1dfa2.js similarity index 83% rename from assets/js/859fcda7.dd618dad.js rename to assets/js/859fcda7.e1a1dfa2.js index 10642891a9..debcf22799 100644 --- a/assets/js/859fcda7.dd618dad.js +++ b/assets/js/859fcda7.e1a1dfa2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9810],{95499:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>c,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started",original_id:"getting-started"},o=void 0,s={unversionedId:"getting-started",id:"version-4.1/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-4.1/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/4.1/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/getting-started.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started",original_id:"getting-started"},sidebar:"version-4.1/docs",previous:{title:"GraphQLite",permalink:"/docs/4.1/"},next:{title:"Symfony bundle",permalink:"/docs/4.1/symfony-bundle"}},d={},l=[],g={toc:l},p="wrapper";function c(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,r.A)({},g,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.1/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.1/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.1/universal_service_providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.1/other-frameworks"},"Get started with another framework (or no framework)"))))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9810],{95499:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>p,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started",original_id:"getting-started"},o=void 0,s={unversionedId:"getting-started",id:"version-4.1/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-4.1/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/4.1/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/getting-started.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started",original_id:"getting-started"},sidebar:"version-4.1/docs",previous:{title:"GraphQLite",permalink:"/docs/4.1/"},next:{title:"Symfony bundle",permalink:"/docs/4.1/symfony-bundle"}},d={},l=[],g={toc:l},c="wrapper";function p(e){let{components:t,...a}=e;return(0,i.yg)(c,(0,r.A)({},g,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.1/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.1/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.1/universal_service_providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.1/other-frameworks"},"Get started with another framework (or no framework)"))))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/85c72337.51288902.js b/assets/js/85c72337.ac0db6d4.js similarity index 99% rename from assets/js/85c72337.51288902.js rename to assets/js/85c72337.ac0db6d4.js index 7db0acacfc..04bae2bf93 100644 --- a/assets/js/85c72337.51288902.js +++ b/assets/js/85c72337.ac0db6d4.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5779],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},25290:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-5.0/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-5.0/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/5.0/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/symfony-bundle-advanced.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"version-5.0/docs",previous:{title:"Class with multiple output types",permalink:"/docs/5.0/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/5.0/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5779],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function d(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function m(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=d(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!m({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return m({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},m=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},25290:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-5.0/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-5.0/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/5.0/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/symfony-bundle-advanced.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},sidebar:"version-5.0/docs",previous:{title:"Class with multiple output types",permalink:"/docs/5.0/multiple-output-types"},next:{title:"Laravel specific features",permalink:"/docs/5.0/laravel-package-advanced"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],d={toc:p},m="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(m,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("div",{class:"alert alert--warning"},(0,l.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Symfony bundle specific documentation, please see the ",(0,l.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-bundle"},"Github repository"),"."),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/87089bce.fd4ba243.js b/assets/js/87089bce.44226929.js similarity index 99% rename from assets/js/87089bce.fd4ba243.js rename to assets/js/87089bce.44226929.js index c58c98ed53..67f1ea6853 100644 --- a/assets/js/87089bce.fd4ba243.js +++ b/assets/js/87089bce.44226929.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4253],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var t=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),l=a(20053),i=a(23104),s=a(56347),p=a(57485),c=a(31682),o=a(89466);function u(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function m(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function d(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[p,c]=g({queryString:a,groupId:t}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=p??u;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),c(e),y(e)}),[c,y,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:p,tabValues:c}=e;const o=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=c[a].value;t!==s&&(u(n),p(t))},d=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:d,onClick:m},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function N(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(v,(0,t.A)({},e,n)))}function T(e){const n=(0,h.A)();return r.createElement(N,(0,t.A)({key:String(n)},e))}},132:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>p,default:()=>g,frontMatter:()=>s,metadata:()=>c,toc:()=>u});var t=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const s={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},p=void 0,c={unversionedId:"inheritance-interfaces",id:"version-7.0.0/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-7.0.0/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/inheritance-interfaces.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"docs",previous:{title:"Input types",permalink:"/docs/input-types"},next:{title:"Error handling",permalink:"/docs/error-handling"}},o={},u=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],m={toc:u},d="wrapper";function g(e){let{components:n,...a}=e;return(0,r.yg)(d,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,r.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,r.yg)("p",null,"Let's say you have two classes, ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,r.yg)("p",null,"Written in ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,r.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,r.yg)("p",null,"The GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,r.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,r.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,r.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,r.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will translate in GraphQL schema as:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,r.yg)("p",null,"Please note that you do not need to put the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,r.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,r.yg)("p",null,"You don't have to explicitly put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")))),(0,r.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,r.yg)("p",null,"In the example above, because the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,r.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4253],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var t=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),l=a(20053),i=a(23104),s=a(56347),p=a(57485),c=a(31682),o=a(89466);function u(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function m(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function d(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[p,c]=g({queryString:a,groupId:t}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=p??u;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),c(e),y(e)}),[c,y,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:p,tabValues:c}=e;const o=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=c[a].value;t!==s&&(u(n),p(t))},d=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:d,onClick:m},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function N(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(v,(0,t.A)({},e,n)))}function T(e){const n=(0,h.A)();return r.createElement(N,(0,t.A)({key:String(n)},e))}},132:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>p,default:()=>g,frontMatter:()=>s,metadata:()=>c,toc:()=>u});var t=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const s={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},p=void 0,c={unversionedId:"inheritance-interfaces",id:"version-7.0.0/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-7.0.0/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/inheritance-interfaces.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"docs",previous:{title:"Input types",permalink:"/docs/input-types"},next:{title:"Error handling",permalink:"/docs/error-handling"}},o={},u=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],m={toc:u},d="wrapper";function g(e){let{components:n,...a}=e;return(0,r.yg)(d,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,r.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,r.yg)("p",null,"Let's say you have two classes, ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,r.yg)("p",null,"Written in ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,r.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,r.yg)("p",null,"The GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,r.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,r.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,r.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,r.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will translate in GraphQL schema as:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,r.yg)("p",null,"Please note that you do not need to put the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,r.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,r.yg)("p",null,"You don't have to explicitly put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")))),(0,r.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,r.yg)("p",null,"In the example above, because the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,r.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8913b51a.b8aea398.js b/assets/js/8913b51a.810e616c.js similarity index 97% rename from assets/js/8913b51a.b8aea398.js rename to assets/js/8913b51a.810e616c.js index 7ed73b9668..f270af6cb8 100644 --- a/assets/js/8913b51a.b8aea398.js +++ b/assets/js/8913b51a.810e616c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7575],{71120:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>n,default:()=>h,frontMatter:()=>i,metadata:()=>r,toc:()=>s});var o=t(58168),l=(t(96540),t(15680));t(67443);const i={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},n=void 0,r={unversionedId:"file-uploads",id:"version-6.1/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-6.1/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/6.1/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/file-uploads.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"docs",previous:{title:"Prefetching records",permalink:"/docs/6.1/prefetch-method"},next:{title:"Pagination",permalink:"/docs/6.1/pagination"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],u={toc:s},d="wrapper";function h(e){let{components:a,...t}=e;return(0,l.yg)(d,(0,o.A)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,l.yg)("h2",{id:"installation"},"Installation"),(0,l.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,l.yg)("p",null,"You must start by installing this package:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,l.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,l.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,l.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,l.yg)("p",null,"In order to use this, you must first be sure that the ",(0,l.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,l.yg)("p",null,"Simply add ",(0,l.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,l.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,l.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,l.yg)("h2",{id:"usage"},"Usage"),(0,l.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,l.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")),(0,l.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,l.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7575],{71120:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>n,default:()=>h,frontMatter:()=>i,metadata:()=>r,toc:()=>s});var o=t(58168),l=(t(96540),t(15680));t(67443);const i={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},n=void 0,r={unversionedId:"file-uploads",id:"version-6.1/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-6.1/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/6.1/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/file-uploads.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"docs",previous:{title:"Prefetching records",permalink:"/docs/6.1/prefetch-method"},next:{title:"Pagination",permalink:"/docs/6.1/pagination"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],u={toc:s},d="wrapper";function h(e){let{components:a,...t}=e;return(0,l.yg)(d,(0,o.A)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,l.yg)("h2",{id:"installation"},"Installation"),(0,l.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,l.yg)("p",null,"You must start by installing this package:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,l.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,l.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,l.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,l.yg)("p",null,"In order to use this, you must first be sure that the ",(0,l.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,l.yg)("p",null,"Simply add ",(0,l.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,l.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,l.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,l.yg)("h2",{id:"usage"},"Usage"),(0,l.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,l.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")),(0,l.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,l.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,l.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/89cae3a7.5acb06f9.js b/assets/js/89cae3a7.05b5165d.js similarity index 98% rename from assets/js/89cae3a7.5acb06f9.js rename to assets/js/89cae3a7.05b5165d.js index b036f8e3ff..560f642cfd 100644 --- a/assets/js/89cae3a7.5acb06f9.js +++ b/assets/js/89cae3a7.05b5165d.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7858],{81295:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},r=void 0,l={unversionedId:"external-type-declaration",id:"external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an attribute on a domain class.",source:"@site/docs/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/next/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/external-type-declaration.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"docs",previous:{title:"Extending a type",permalink:"/docs/next/extend-type"},next:{title:"Input types",permalink:"/docs/next/input-types"}},s={},u=[{value:"#[Type] attribute with the class attribute",id:"type-attribute-with-the-class-attribute",level:2},{value:"#[SourceField] attribute",id:"sourcefield-attribute",level:2},{value:"#[MagicField] attribute",id:"magicfield-attribute",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without attributes)",id:"declaring-fields-dynamically-without-attributes",level:2}],d={toc:u},p="wrapper";function c(e){let{components:t,...n}=e;return(0,i.yg)(p,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In some cases, you cannot or do not want to put an attribute on a domain class."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,i.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with attributes from the view layer"),(0,i.yg)("li",{parentName:"ul"},"etc.")),(0,i.yg)("h2",{id:"type-attribute-with-the-class-attribute"},(0,i.yg)("inlineCode",{parentName:"h2"},"#[Type]")," attribute with the ",(0,i.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,i.yg)("p",null,"GraphQLite allows you to use a ",(0,i.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute with the ",(0,i.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,i.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,i.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,i.yg)("p",null,"In methods with a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute, the first parameter is the ",(0,i.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,i.yg)("h2",{id:"sourcefield-attribute"},(0,i.yg)("inlineCode",{parentName:"h2"},"#[SourceField]")," attribute"),(0,i.yg)("p",null,"If you don't want to rewrite all ",(0,i.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[SourceField]")," attribute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n')),(0,i.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,i.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,i.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,i.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"isName()"),").\nYou can set different name to look for with ",(0,i.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,i.yg)("h2",{id:"magicfield-attribute"},(0,i.yg)("inlineCode",{parentName:"h2"},"#[MagicField]")," attribute"),(0,i.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,i.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[MagicField]")," attribute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')),(0,i.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,i.yg)("inlineCode",{parentName:"p"},"Product")," object.\nYou can set different name to look for with ",(0,i.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,i.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,i.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,i.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,i.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" argument.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price", annotations: [new Logged(), new Right("CAN_ACCESS_Price"), new FailWith(null)])]\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,i.yg)("p",null,"Any attributes described in the ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/authentication-authorization"},"Authentication and authorization page"),", or any attribute this is actually a ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/field-middlewares"},'"field middleware"')," can be used in the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[SourceField]"),' "annotations" argument.'),(0,i.yg)("h2",{id:"declaring-fields-dynamically-without-attributes"},"Declaring fields dynamically (without attributes)"),(0,i.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,i.yg)("inlineCode",{parentName:"p"},"#[SourceField]")," attributes at development time.\nIf you need to decide the list of ",(0,i.yg)("inlineCode",{parentName:"p"},"#[SourceField]")," at runtime, you can implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'annotations'=>[new Logged()]]),\n ];\n } else {\n return [];\n }\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7858],{81295:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},r=void 0,l={unversionedId:"external-type-declaration",id:"external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an attribute on a domain class.",source:"@site/docs/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/next/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/external-type-declaration.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"docs",previous:{title:"Extending a type",permalink:"/docs/next/extend-type"},next:{title:"Input types",permalink:"/docs/next/input-types"}},s={},u=[{value:"#[Type] attribute with the class attribute",id:"type-attribute-with-the-class-attribute",level:2},{value:"#[SourceField] attribute",id:"sourcefield-attribute",level:2},{value:"#[MagicField] attribute",id:"magicfield-attribute",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without attributes)",id:"declaring-fields-dynamically-without-attributes",level:2}],d={toc:u},p="wrapper";function c(e){let{components:t,...n}=e;return(0,i.yg)(p,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In some cases, you cannot or do not want to put an attribute on a domain class."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,i.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with attributes from the view layer"),(0,i.yg)("li",{parentName:"ul"},"etc.")),(0,i.yg)("h2",{id:"type-attribute-with-the-class-attribute"},(0,i.yg)("inlineCode",{parentName:"h2"},"#[Type]")," attribute with the ",(0,i.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,i.yg)("p",null,"GraphQLite allows you to use a ",(0,i.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute with the ",(0,i.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,i.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,i.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,i.yg)("p",null,"In methods with a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute, the first parameter is the ",(0,i.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,i.yg)("h2",{id:"sourcefield-attribute"},(0,i.yg)("inlineCode",{parentName:"h2"},"#[SourceField]")," attribute"),(0,i.yg)("p",null,"If you don't want to rewrite all ",(0,i.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[SourceField]")," attribute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n')),(0,i.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,i.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,i.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,i.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"isName()"),").\nYou can set different name to look for with ",(0,i.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,i.yg)("h2",{id:"magicfield-attribute"},(0,i.yg)("inlineCode",{parentName:"h2"},"#[MagicField]")," attribute"),(0,i.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,i.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[MagicField]")," attribute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')),(0,i.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,i.yg)("inlineCode",{parentName:"p"},"Product")," object.\nYou can set different name to look for with ",(0,i.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,i.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,i.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,i.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,i.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" argument.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price", annotations: [new Logged(), new Right("CAN_ACCESS_Price"), new FailWith(null)])]\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,i.yg)("p",null,"Any attributes described in the ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/authentication-authorization"},"Authentication and authorization page"),", or any attribute this is actually a ",(0,i.yg)("a",{parentName:"p",href:"/docs/next/field-middlewares"},'"field middleware"')," can be used in the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[SourceField]"),' "annotations" argument.'),(0,i.yg)("h2",{id:"declaring-fields-dynamically-without-attributes"},"Declaring fields dynamically (without attributes)"),(0,i.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,i.yg)("inlineCode",{parentName:"p"},"#[SourceField]")," attributes at development time.\nIf you need to decide the list of ",(0,i.yg)("inlineCode",{parentName:"p"},"#[SourceField]")," at runtime, you can implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'annotations'=>[new Logged()]]),\n ];\n } else {\n return [];\n }\n }\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/89ed63c8.fc9f93b4.js b/assets/js/89ed63c8.d51a0220.js similarity index 94% rename from assets/js/89ed63c8.fc9f93b4.js rename to assets/js/89ed63c8.d51a0220.js index 769dce3387..4ccbbd64fb 100644 --- a/assets/js/89ed63c8.fc9f93b4.js +++ b/assets/js/89ed63c8.d51a0220.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[573],{19365:(e,n,t)=>{t.d(n,{A:()=>o});var a=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>I});var a=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function p(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??d(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function h(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=g({queryString:t,groupId:a}),[d,m]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),y=(()=>{const e=u??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),m(e)}),[s,m,i]),tabValues:i}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(d(n),u(a))},h=e=>{let n=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function N(e){const n=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function I(e){const n=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(n)},e))}},74275:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>g,frontMatter:()=>l,metadata:()=>s,toc:()=>d});var a=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},u=void 0,s={unversionedId:"authentication-authorization",id:"version-4.3/authentication-authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-4.3/authentication-authorization.mdx",sourceDirName:".",slug:"/authentication-authorization",permalink:"/docs/4.3/authentication-authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/authentication-authorization.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},sidebar:"version-4.3/docs",previous:{title:"User input validation",permalink:"/docs/4.3/validation"},next:{title:"Fine grained security",permalink:"/docs/4.3/fine-grained-security"}},c={},d=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],p={toc:d},h="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(h,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.3/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[573],{19365:(e,n,t)=>{t.d(n,{A:()=>o});var a=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>I});var a=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function h(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??d(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function p(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=h(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!p({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=g({queryString:t,groupId:a}),[d,m]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),y=(()=>{const e=u??d;return p({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!p({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),m(e)}),[s,m,i]),tabValues:i}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(d(n),u(a))},p=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:p,onClick:h},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function N(e){const n=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function I(e){const n=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(n)},e))}},74275:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>g,frontMatter:()=>l,metadata:()=>s,toc:()=>d});var a=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},u=void 0,s={unversionedId:"authentication-authorization",id:"version-4.3/authentication-authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-4.3/authentication-authorization.mdx",sourceDirName:".",slug:"/authentication-authorization",permalink:"/docs/4.3/authentication-authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/authentication-authorization.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},sidebar:"version-4.3/docs",previous:{title:"User input validation",permalink:"/docs/4.3/validation"},next:{title:"Fine grained security",permalink:"/docs/4.3/fine-grained-security"}},c={},d=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],h={toc:d},p="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(p,(0,a.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.3/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8b6bafea.4c53dd2a.js b/assets/js/8b6bafea.69028f9b.js similarity index 98% rename from assets/js/8b6bafea.4c53dd2a.js rename to assets/js/8b6bafea.69028f9b.js index 12cd182394..c298533e16 100644 --- a/assets/js/8b6bafea.4c53dd2a.js +++ b/assets/js/8b6bafea.69028f9b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2230],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),l=n(23104),o=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),b=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},l,{className:(0,s.A)("tabs__item",y.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",y.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,b.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},74128:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>o,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),l=n(19365);const o={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,u={unversionedId:"prefetch-method",id:"version-5.0/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-5.0/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/5.0/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/prefetch-method.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"version-5.0/docs",previous:{title:"Query plan",permalink:"/docs/5.0/query-plan"},next:{title:"File uploads",permalink:"/docs/5.0/file-uploads"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))),(0,r.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2230],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),l=n(23104),o=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),b=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},l,{className:(0,s.A)("tabs__item",y.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",y.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,b.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},74128:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>o,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),l=n(19365);const o={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,u={unversionedId:"prefetch-method",id:"version-5.0/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-5.0/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/5.0/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/prefetch-method.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"version-5.0/docs",previous:{title:"Query plan",permalink:"/docs/5.0/query-plan"},next:{title:"File uploads",permalink:"/docs/5.0/file-uploads"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))),(0,r.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8bf32d27.228eb7dc.js b/assets/js/8bf32d27.3378bf47.js similarity index 98% rename from assets/js/8bf32d27.228eb7dc.js rename to assets/js/8bf32d27.3378bf47.js index 3a11d1b229..e149c7e1f8 100644 --- a/assets/js/8bf32d27.228eb7dc.js +++ b/assets/js/8bf32d27.3378bf47.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6523],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},12215:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},u=void 0,s={unversionedId:"extend-input-type",id:"version-4.3/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.3/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/4.3/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/extend-input-type.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"version-4.3/docs",previous:{title:"Custom argument resolving",permalink:"/docs/4.3/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/4.3/multiple-output-types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6523],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},12215:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},u=void 0,s={unversionedId:"extend-input-type",id:"version-4.3/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.3/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/4.3/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/extend-input-type.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"version-4.3/docs",previous:{title:"Custom argument resolving",permalink:"/docs/4.3/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/4.3/multiple-output-types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8c95fc16.b5b3d6f8.js b/assets/js/8c95fc16.32ab2a8a.js similarity index 99% rename from assets/js/8c95fc16.b5b3d6f8.js rename to assets/js/8c95fc16.32ab2a8a.js index 0c3ce39ef9..41693b6aa0 100644 --- a/assets/js/8c95fc16.b5b3d6f8.js +++ b/assets/js/8c95fc16.32ab2a8a.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9158],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),i=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},98445:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const i={id:"queries",title:"Queries",sidebar_label:"Queries"},s=void 0,u={unversionedId:"queries",id:"version-5.0/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-5.0/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/5.0/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/queries.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"version-5.0/docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/5.0/other-frameworks"},next:{title:"Mutations",permalink:"/docs/5.0/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...i}=e;return(0,r.yg)(h,(0,a.A)({},d,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9158],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),i=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},98445:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const i={id:"queries",title:"Queries",sidebar_label:"Queries"},s=void 0,u={unversionedId:"queries",id:"version-5.0/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-5.0/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/5.0/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/queries.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"version-5.0/docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/5.0/other-frameworks"},next:{title:"Mutations",permalink:"/docs/5.0/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...i}=e;return(0,r.yg)(h,(0,a.A)({},d,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file diff --git a/assets/js/8d81badd.64591638.js b/assets/js/8d81badd.0c6fdf6d.js similarity index 96% rename from assets/js/8d81badd.64591638.js rename to assets/js/8d81badd.0c6fdf6d.js index 77813fee4a..27bcd51f5f 100644 --- a/assets/js/8d81badd.64591638.js +++ b/assets/js/8d81badd.0c6fdf6d.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3701],{15793:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>g,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var t=n(58168),r=(n(96540),n(15680));n(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-6.0/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.0/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/6.0/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/argument-resolving.md",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"docs",previous:{title:"Custom annotations",permalink:"/docs/6.0/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/6.0/extend-input-type"}},s={},p=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},d="wrapper";function g(e){let{components:a,...n}=e;return(0,r.yg)(d,(0,t.A)({},m,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Autowire")," annotation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3701],{15793:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var t=n(58168),r=(n(96540),n(15680));n(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-6.0/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.0/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/6.0/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/argument-resolving.md",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"docs",previous:{title:"Custom annotations",permalink:"/docs/6.0/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/6.0/extend-input-type"}},s={},p=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},g="wrapper";function d(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},m,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Autowire")," annotation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8f7abfe1.4744fc25.js b/assets/js/8f7abfe1.efb01d30.js similarity index 98% rename from assets/js/8f7abfe1.4744fc25.js rename to assets/js/8f7abfe1.efb01d30.js index c65d90ec0b..008fc8cf42 100644 --- a/assets/js/8f7abfe1.4744fc25.js +++ b/assets/js/8f7abfe1.efb01d30.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2592],{21703:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>h,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var a=t(58168),r=(t(96540),t(15680));t(67443);const i={id:"queries",title:"Queries",sidebar_label:"Queries",original_id:"queries"},l=void 0,o={unversionedId:"queries",id:"version-3.0/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-3.0/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/3.0/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/queries.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries",original_id:"queries"},sidebar:"version-3.0/docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/3.0/other-frameworks"},next:{title:"Mutations",permalink:"/docs/3.0/mutations"}},s={},p=[{value:"Simple query",id:"simple-query",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],u={toc:p},y="wrapper";function h(e){let{components:n,...i}=e;return(0,r.yg)(y,(0,a.A)({},u,i,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:t(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}h.isMDXComponent=!0},67258:(e,n,t)=>{t.d(n,{A:()=>a});const a=t.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2592],{21703:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>h,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var a=t(58168),r=(t(96540),t(15680));t(67443);const i={id:"queries",title:"Queries",sidebar_label:"Queries",original_id:"queries"},l=void 0,o={unversionedId:"queries",id:"version-3.0/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-3.0/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/3.0/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/queries.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries",original_id:"queries"},sidebar:"version-3.0/docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/3.0/other-frameworks"},next:{title:"Mutations",permalink:"/docs/3.0/mutations"}},s={},p=[{value:"Simple query",id:"simple-query",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],u={toc:p},y="wrapper";function h(e){let{components:n,...i}=e;return(0,r.yg)(y,(0,a.A)({},u,i,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:t(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}h.isMDXComponent=!0},67258:(e,n,t)=>{t.d(n,{A:()=>a});const a=t.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file diff --git a/assets/js/8f7fa040.1e12c214.js b/assets/js/8f7fa040.51146fec.js similarity index 95% rename from assets/js/8f7fa040.1e12c214.js rename to assets/js/8f7fa040.51146fec.js index a6a5524102..9c993d16ac 100644 --- a/assets/js/8f7fa040.1e12c214.js +++ b/assets/js/8f7fa040.51146fec.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1505],{23511:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>o,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"extend_type",title:"Extending a type",sidebar_label:"Extending a type",original_id:"extend_type"},o=void 0,l={unversionedId:"extend_type",id:"version-4.0/extend_type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-4.0/extend_type.mdx",sourceDirName:".",slug:"/extend_type",permalink:"/docs/4.0/extend_type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/extend_type.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend_type",title:"Extending a type",sidebar_label:"Extending a type",original_id:"extend_type"},sidebar:"version-4.0/docs",previous:{title:"Autowiring services",permalink:"/docs/4.0/autowiring"},next:{title:"External type declaration",permalink:"/docs/4.0/external_type_declaration"}},s={},p=[],d={toc:p},c="wrapper";function u(e){let{components:n,...t}=e;return(0,i.yg)(c,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,i.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,i.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,i.yg)("p",null,"Let's assume you have a ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,i.yg)("inlineCode",{parentName:"p"},"getName()")," method in the product because the name needs to be translated in the correct language. You have a ",(0,i.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,i.yg)("p",null,"Using ",(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,i.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")),(0,i.yg)("p",null,"Let's break this sample:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")),(0,i.yg)("p",null,"With the ",(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,i.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,i.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,i.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,i.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,i.yg)("p",null,'Using the "',(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,i.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1505],{23511:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"extend_type",title:"Extending a type",sidebar_label:"Extending a type",original_id:"extend_type"},l=void 0,o={unversionedId:"extend_type",id:"version-4.0/extend_type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-4.0/extend_type.mdx",sourceDirName:".",slug:"/extend_type",permalink:"/docs/4.0/extend_type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/extend_type.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend_type",title:"Extending a type",sidebar_label:"Extending a type",original_id:"extend_type"},sidebar:"version-4.0/docs",previous:{title:"Autowiring services",permalink:"/docs/4.0/autowiring"},next:{title:"External type declaration",permalink:"/docs/4.0/external_type_declaration"}},s={},p=[],d={toc:p},c="wrapper";function u(e){let{components:n,...t}=e;return(0,i.yg)(c,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,i.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,i.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,i.yg)("p",null,"Let's assume you have a ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,i.yg)("inlineCode",{parentName:"p"},"getName()")," method in the product because the name needs to be translated in the correct language. You have a ",(0,i.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,i.yg)("p",null,"Using ",(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,i.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")),(0,i.yg)("p",null,"Let's break this sample:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")),(0,i.yg)("p",null,"With the ",(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,i.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,i.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,i.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,i.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,i.yg)("p",null,'Using the "',(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,i.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8f951ce3.38a0ed05.js b/assets/js/8f951ce3.a721f857.js similarity index 98% rename from assets/js/8f951ce3.38a0ed05.js rename to assets/js/8f951ce3.a721f857.js index 5bf12eabe8..a2571c384c 100644 --- a/assets/js/8f951ce3.38a0ed05.js +++ b/assets/js/8f951ce3.a721f857.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9439],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),l=n(23104),o=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),b=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},l,{className:(0,s.A)("tabs__item",y.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",y.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,b.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},88067:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>o,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),l=n(19365);const o={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,u={unversionedId:"prefetch-method",id:"version-4.2/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-4.2/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/4.2/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/prefetch-method.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"version-4.2/docs",previous:{title:"Query plan",permalink:"/docs/4.2/query-plan"},next:{title:"File uploads",permalink:"/docs/4.2/file-uploads"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))),(0,r.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9439],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),l=n(23104),o=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),b=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},l,{className:(0,s.A)("tabs__item",y.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",y.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,b.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},88067:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>o,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),l=n(19365);const o={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,u={unversionedId:"prefetch-method",id:"version-4.2/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-4.2/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/4.2/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/prefetch-method.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"version-4.2/docs",previous:{title:"Query plan",permalink:"/docs/4.2/query-plan"},next:{title:"File uploads",permalink:"/docs/4.2/file-uploads"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))),(0,r.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/8f967659.f5d72090.js b/assets/js/8f967659.6ddd2ee0.js similarity index 96% rename from assets/js/8f967659.f5d72090.js rename to assets/js/8f967659.6ddd2ee0.js index d2f16ba288..fecf64fbbc 100644 --- a/assets/js/8f967659.f5d72090.js +++ b/assets/js/8f967659.6ddd2ee0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[109],{28185:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-3.0/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all",source:"@site/versioned_docs/version-3.0/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/3.0/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/semver.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"}},l={},p=[],m={toc:p},u="wrapper";function d(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all\nminor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short,\nSemantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility.\nMinor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of\nthat release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental"),"\nand their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a major version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a minor version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library.\nThey are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[109],{28185:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"version-3.0/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all",source:"@site/versioned_docs/version-3.0/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/3.0/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/semver.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"}},l={},p=[],m={toc:p},u="wrapper";function d(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all\nminor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short,\nSemantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility.\nMinor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of\nthat release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental"),"\nand their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a major version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a minor version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library.\nThey are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9000b231.b513fb14.js b/assets/js/9000b231.ae4fd67e.js similarity index 88% rename from assets/js/9000b231.b513fb14.js rename to assets/js/9000b231.ae4fd67e.js index 917ecb02a8..76431854ab 100644 --- a/assets/js/9000b231.b513fb14.js +++ b/assets/js/9000b231.ae4fd67e.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2032],{1727:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>d});var n=t(58168),r=(t(96540),t(15680));t(67443);const i={id:"migrating",title:"Release notes",sidebar_label:"Release notes",original_id:"migrating"},l=void 0,o={unversionedId:"migrating",id:"version-3.0/migrating",title:"Release notes",description:"First stable release of GraphQLite",source:"@site/versioned_docs/version-3.0/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/3.0/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/migrating.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"migrating",title:"Release notes",sidebar_label:"Release notes",original_id:"migrating"}},s={},d=[{value:"First stable release of GraphQLite",id:"first-stable-release-of-graphqlite",level:2},{value:"Basic example",id:"basic-example",level:2}],p={toc:d},g="wrapper";function u(e){let{components:a,...t}=e;return(0,r.yg)(g,(0,n.A)({},p,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"first-stable-release-of-graphqlite"},"First stable release of GraphQLite"),(0,r.yg)("p",null,"GraphQLite is PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony and Laravel bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, extendable types and more!")),(0,r.yg)("p",null,"After several months of work, we are very happy to announce the availability of GraphQLite v3.0."),(0,r.yg)("p",null,'If you are wondering where are v1 and v2... yeah... GraphQLite is a fork of "thecodingmachine/graphql-controllers" that already had a v1 and a v2. But so much has changed that it deserved a new name!'),(0,r.yg)("p",null,(0,r.yg)("a",{parentName:"p",href:"https://graphqlite.thecodingmachine.io"},"Check out the documentation")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")),(0,r.yg)("p",null,"That's it, you're good to go \ud83c\udf89! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2032],{1727:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>p});var n=t(58168),r=(t(96540),t(15680));t(67443);const i={id:"migrating",title:"Release notes",sidebar_label:"Release notes",original_id:"migrating"},l=void 0,o={unversionedId:"migrating",id:"version-3.0/migrating",title:"Release notes",description:"First stable release of GraphQLite",source:"@site/versioned_docs/version-3.0/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/3.0/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/migrating.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"migrating",title:"Release notes",sidebar_label:"Release notes",original_id:"migrating"}},s={},p=[{value:"First stable release of GraphQLite",id:"first-stable-release-of-graphqlite",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:p},g="wrapper";function u(e){let{components:a,...t}=e;return(0,r.yg)(g,(0,n.A)({},d,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"first-stable-release-of-graphqlite"},"First stable release of GraphQLite"),(0,r.yg)("p",null,"GraphQLite is PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony and Laravel bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, extendable types and more!")),(0,r.yg)("p",null,"After several months of work, we are very happy to announce the availability of GraphQLite v3.0."),(0,r.yg)("p",null,'If you are wondering where are v1 and v2... yeah... GraphQLite is a fork of "thecodingmachine/graphql-controllers" that already had a v1 and a v2. But so much has changed that it deserved a new name!'),(0,r.yg)("p",null,(0,r.yg)("a",{parentName:"p",href:"https://graphqlite.thecodingmachine.io"},"Check out the documentation")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")),(0,r.yg)("p",null,"That's it, you're good to go \ud83c\udf89! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9073923c.5bec0ca2.js b/assets/js/9073923c.d4d97409.js similarity index 97% rename from assets/js/9073923c.5bec0ca2.js rename to assets/js/9073923c.d4d97409.js index faf489bf66..8f36ead2b7 100644 --- a/assets/js/9073923c.5bec0ca2.js +++ b/assets/js/9073923c.d4d97409.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4017],{23328:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>d,frontMatter:()=>s,metadata:()=>i,toc:()=>l});var n=a(58168),p=(a(96540),a(15680));a(67443);const s={id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"},o=void 0,i={unversionedId:"custom-output-types",id:"version-4.1/custom-output-types",title:"Custom output types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-4.1/custom_output_types.md",sourceDirName:".",slug:"/custom-output-types",permalink:"/docs/4.1/custom-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/custom_output_types.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"}},u={},l=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3}],r={toc:l},y="wrapper";function d(e){let{components:t,...a}=e;return(0,p.yg)(y,(0,n.A)({},r,a,{components:t,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field(name="id")\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n')),(0,p.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,p.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,p.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,p.yg)("p",null,"GraphQL comes with an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,p.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID!")\n */\n')),(0,p.yg)("h2",{id:"usage"},"Usage"),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,p.yg)("p",null,"You can use the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Query")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Field")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@SourceField"))),(0,p.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,p.yg)("p",null,"In order to create a custom output type, you need to:"),(0,p.yg)("ol",null,(0,p.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,p.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,p.yg)("p",null,"You'll find more details on the ",(0,p.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,p.yg)("hr",null),(0,p.yg)("p",null,"In order to find existing types, the schema is using ",(0,p.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,p.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,p.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,p.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,p.yg)("p",null,"Any class extending ",(0,p.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,p.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,p.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,p.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,p.yg)("p",null,"The easiest way is to use a ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". This class is used to register custom output types."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper")," instance MUST be registered in your container and linked to a ",(0,p.yg)("inlineCode",{parentName:"p"},"CompositeTypeMapper"),"\nthat will aggregate all the type mappers of the application."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4017],{23328:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>d,frontMatter:()=>s,metadata:()=>i,toc:()=>l});var n=a(58168),p=(a(96540),a(15680));a(67443);const s={id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"},o=void 0,i={unversionedId:"custom-output-types",id:"version-4.1/custom-output-types",title:"Custom output types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-4.1/custom_output_types.md",sourceDirName:".",slug:"/custom-output-types",permalink:"/docs/4.1/custom-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/custom_output_types.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-output-types",title:"Custom output types",sidebar_label:"Custom output types",original_id:"custom-output-types"}},u={},l=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3}],r={toc:l},y="wrapper";function d(e){let{components:t,...a}=e;return(0,p.yg)(y,(0,n.A)({},r,a,{components:t,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field(name="id")\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n')),(0,p.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,p.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,p.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,p.yg)("p",null,"GraphQL comes with an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,p.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID!")\n */\n')),(0,p.yg)("h2",{id:"usage"},"Usage"),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,p.yg)("p",null,"You can use the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Query")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Field")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@SourceField"))),(0,p.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,p.yg)("p",null,"In order to create a custom output type, you need to:"),(0,p.yg)("ol",null,(0,p.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,p.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,p.yg)("p",null,"You'll find more details on the ",(0,p.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,p.yg)("hr",null),(0,p.yg)("p",null,"In order to find existing types, the schema is using ",(0,p.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,p.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,p.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,p.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,p.yg)("p",null,"Any class extending ",(0,p.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,p.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,p.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,p.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,p.yg)("p",null,"The easiest way is to use a ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". This class is used to register custom output types."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper")," instance MUST be registered in your container and linked to a ",(0,p.yg)("inlineCode",{parentName:"p"},"CompositeTypeMapper"),"\nthat will aggregate all the type mappers of the application."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/90e0b7fd.b13844c0.js b/assets/js/90e0b7fd.c8a731fc.js similarity index 98% rename from assets/js/90e0b7fd.b13844c0.js rename to assets/js/90e0b7fd.c8a731fc.js index eee8fd4ca4..e54c525f7c 100644 --- a/assets/js/90e0b7fd.b13844c0.js +++ b/assets/js/90e0b7fd.c8a731fc.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5856],{28565:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"version-6.1/laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.1/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/6.1/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/laravel-package.md",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"docs",previous:{title:"Symfony bundle",permalink:"/docs/6.1/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/6.1/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},'$ php artisan vendor:publish --provider="TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider"\n')),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5856],{28565:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"version-6.1/laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.1/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/6.1/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/laravel-package.md",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"docs",previous:{title:"Symfony bundle",permalink:"/docs/6.1/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/6.1/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},'$ php artisan vendor:publish --provider="TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider"\n')),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9206a32f.5e372737.js b/assets/js/9206a32f.685678c0.js similarity index 98% rename from assets/js/9206a32f.5e372737.js rename to assets/js/9206a32f.685678c0.js index ba6ca7f9f8..53821cb72a 100644 --- a/assets/js/9206a32f.5e372737.js +++ b/assets/js/9206a32f.685678c0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7696],{46284:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating"},r=void 0,l={unversionedId:"migrating",id:"version-6.0/migrating",title:"Migrating",description:"Migrating from v4.0 to v4.1",source:"@site/versioned_docs/version-6.0/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/6.0/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/migrating.md",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating"},sidebar:"docs",previous:{title:"Troubleshooting",permalink:"/docs/6.0/troubleshooting"},next:{title:"Annotations VS Attributes",permalink:"/docs/6.0/doctrine-annotations-attributes"}},s={},g=[{value:"Migrating from v4.0 to v4.1",id:"migrating-from-v40-to-v41",level:2},{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},p="wrapper";function u(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v40-to-v41"},"Migrating from v4.0 to v4.1"),(0,i.yg)("p",null,"GraphQLite follows Semantic Versioning. GraphQLite 4.1 is backward compatible with GraphQLite 4.0. See\n",(0,i.yg)("a",{parentName:"p",href:"/docs/6.0/semver"},"semantic versioning")," for more details."),(0,i.yg)("p",null,"There is one exception though: the ",(0,i.yg)("strong",{parentName:"p"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL\ninput types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"p"},"composer.json")," by running this command:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/annotations-reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7696],{46284:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating"},r=void 0,l={unversionedId:"migrating",id:"version-6.0/migrating",title:"Migrating",description:"Migrating from v4.0 to v4.1",source:"@site/versioned_docs/version-6.0/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/6.0/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/migrating.md",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating"},sidebar:"docs",previous:{title:"Troubleshooting",permalink:"/docs/6.0/troubleshooting"},next:{title:"Annotations VS Attributes",permalink:"/docs/6.0/doctrine-annotations-attributes"}},s={},g=[{value:"Migrating from v4.0 to v4.1",id:"migrating-from-v40-to-v41",level:2},{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},p="wrapper";function u(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v40-to-v41"},"Migrating from v4.0 to v4.1"),(0,i.yg)("p",null,"GraphQLite follows Semantic Versioning. GraphQLite 4.1 is backward compatible with GraphQLite 4.0. See\n",(0,i.yg)("a",{parentName:"p",href:"/docs/6.0/semver"},"semantic versioning")," for more details."),(0,i.yg)("p",null,"There is one exception though: the ",(0,i.yg)("strong",{parentName:"p"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL\ninput types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"p"},"composer.json")," by running this command:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/6.0/annotations-reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9279cea7.66cbfaa8.js b/assets/js/9279cea7.f1aa6c8c.js similarity index 99% rename from assets/js/9279cea7.66cbfaa8.js rename to assets/js/9279cea7.f1aa6c8c.js index 6a1ac781a5..f34783c480 100644 --- a/assets/js/9279cea7.66cbfaa8.js +++ b/assets/js/9279cea7.f1aa6c8c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9262],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[s,u]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=s??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),h(e)}),[u,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=u[a].value;n!==l&&(y(t),s(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},31950:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types",original_id:"custom-types"},s=void 0,u={unversionedId:"custom-types",id:"version-4.1/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-4.1/custom_types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/4.1/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/custom_types.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types",original_id:"custom-types"},sidebar:"version-4.1/docs",previous:{title:"Pagination",permalink:"/docs/4.1/pagination"},next:{title:"Custom annotations",permalink:"/docs/4.1/field-middlewares"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): OutputType;\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9262],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[s,u]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=s??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),h(e)}),[u,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=u[a].value;n!==l&&(y(t),s(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},31950:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types",original_id:"custom-types"},s=void 0,u={unversionedId:"custom-types",id:"version-4.1/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-4.1/custom_types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/4.1/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/custom_types.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types",original_id:"custom-types"},sidebar:"version-4.1/docs",previous:{title:"Pagination",permalink:"/docs/4.1/pagination"},next:{title:"Custom annotations",permalink:"/docs/4.1/field-middlewares"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): OutputType;\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9462.462ab9a2.js b/assets/js/9462.462ab9a2.js deleted file mode 100644 index 01fed09d33..0000000000 --- a/assets/js/9462.462ab9a2.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9462],{9462:(e,t,r)=>{function n(e,t){var r=void 0;return function(){for(var n=arguments.length,o=new Array(n),i=0;ivn});var a=function(){};function c(e){var t=e.item,r=e.items;return{index:t.__autocomplete_indexName,items:[t],positions:[1+r.findIndex((function(e){return e.objectID===t.objectID}))],queryID:t.__autocomplete_queryID,algoliaSource:["autocomplete"]}}function l(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var r=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var n,o,i,a,c=[],l=!0,u=!1;try{if(i=(r=r.call(e)).next,0===t){if(Object(r)!==r)return;l=!1}else for(;!(l=(n=i.call(r)).done)&&(c.push(n.value),c.length!==t);l=!0);}catch(s){u=!0,o=s}finally{try{if(!l&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(u)throw o}}return c}}(e,t)||function(e,t){if(!e)return;if("string"==typeof e)return u(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);"Object"===r&&e.constructor&&(r=e.constructor.name);if("Map"===r||"Set"===r)return Array.from(e);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return u(e,t)}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function u(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);re.length)&&(t=e.length);for(var r=0,n=new Array(t);r=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function y(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function h(e){for(var t=1;t=3||2===r&&n>=4||1===r&&n>=10);function i(t,r,n){if(o&&void 0!==n){var i=n[0].__autocomplete_algoliaCredentials,a={"X-Algolia-Application-Id":i.appId,"X-Algolia-API-Key":i.apiKey};e.apply(void 0,[t].concat(p(r),[{headers:a}]))}else e.apply(void 0,[t].concat(p(r)))}return{init:function(t,r){e("init",{appId:t,apiKey:r})},setUserToken:function(t){e("setUserToken",t)},clickedObjectIDsAfterSearch:function(){for(var e=arguments.length,t=new Array(e),r=0;r0&&i("clickedObjectIDsAfterSearch",g(t),t[0].items)},clickedObjectIDs:function(){for(var e=arguments.length,t=new Array(e),r=0;r0&&i("clickedObjectIDs",g(t),t[0].items)},clickedFilters:function(){for(var t=arguments.length,r=new Array(t),n=0;n0&&e.apply(void 0,["clickedFilters"].concat(r))},convertedObjectIDsAfterSearch:function(){for(var e=arguments.length,t=new Array(e),r=0;r0&&i("convertedObjectIDsAfterSearch",g(t),t[0].items)},convertedObjectIDs:function(){for(var e=arguments.length,t=new Array(e),r=0;r0&&i("convertedObjectIDs",g(t),t[0].items)},convertedFilters:function(){for(var t=arguments.length,r=new Array(t),n=0;n0&&e.apply(void 0,["convertedFilters"].concat(r))},viewedObjectIDs:function(){for(var e=arguments.length,t=new Array(e),r=0;r0&&t.reduce((function(e,t){var r=t.items,n=d(t,f);return[].concat(p(e),p(function(e){for(var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:20,r=[],n=0;n0&&e.apply(void 0,["viewedFilters"].concat(r))}}}function S(e){var t=e.items.reduce((function(e,t){var r;return e[t.__autocomplete_indexName]=(null!==(r=e[t.__autocomplete_indexName])&&void 0!==r?r:[]).concat(t),e}),{});return Object.keys(t).map((function(e){return{index:e,items:t[e],algoliaSource:["autocomplete"]}}))}function j(e){return e.objectID&&e.__autocomplete_indexName&&e.__autocomplete_queryID}function w(e){return w="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},w(e)}function E(e){return function(e){if(Array.isArray(e))return P(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return P(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);"Object"===r&&e.constructor&&(r=e.constructor.name);if("Map"===r||"Set"===r)return Array.from(e);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return P(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function P(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r0&&C({onItemsChange:o,items:r,insights:f,state:t}))}}),0);return{name:"aa.algoliaInsightsPlugin",subscribe:function(e){var t=e.setContext,r=e.onSelect,n=e.onActive;s("addAlgoliaAgent","insights-plugin"),t({algoliaInsightsPlugin:{__algoliaSearchParameters:{clickAnalytics:!0},insights:f}}),r((function(e){var t=e.item,r=e.state,n=e.event;j(t)&&l({state:r,event:n,insights:f,item:t,insightsEvents:[D({eventName:"Item Selected"},c({item:t,items:m.current}))]})})),n((function(e){var t=e.item,r=e.state,n=e.event;j(t)&&u({state:r,event:n,insights:f,item:t,insightsEvents:[D({eventName:"Item Active"},c({item:t,items:m.current}))]})}))},onStateChange:function(e){var t=e.state;p({state:t})},__autocomplete_pluginOptions:e}}function _(e){return _="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},_(e)}function T(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function q(e,t,r){return(t=function(e){var t=function(e,t){if("object"!==_(e)||null===e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var n=r.call(e,t||"default");if("object"!==_(n))return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"===_(t)?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function R(e,t,r){var n,o=t.initialState;return{getState:function(){return o},dispatch:function(n,i){var a=function(e){for(var t=1;te.length)&&(t=e.length);for(var r=0,n=new Array(t);r0},reshape:function(e){return e.sources}},e),{},{id:null!==(r=e.id)&&void 0!==r?r:"autocomplete-".concat(V++),plugins:o,initialState:X({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},e.initialState),onStateChange:function(t){var r;null===(r=e.onStateChange)||void 0===r||r.call(e,t),o.forEach((function(e){var r;return null===(r=e.onStateChange)||void 0===r?void 0:r.call(e,t)}))},onSubmit:function(t){var r;null===(r=e.onSubmit)||void 0===r||r.call(e,t),o.forEach((function(e){var r;return null===(r=e.onSubmit)||void 0===r?void 0:r.call(e,t)}))},onReset:function(t){var r;null===(r=e.onReset)||void 0===r||r.call(e,t),o.forEach((function(e){var r;return null===(r=e.onReset)||void 0===r?void 0:r.call(e,t)}))},getSources:function(r){return Promise.all([].concat(Q(o.map((function(e){return e.getSources}))),[e.getSources]).filter(Boolean).map((function(e){return function(e,t){var r=[];return Promise.resolve(e(t)).then((function(e){return Array.isArray(e),Promise.all(e.filter((function(e){return Boolean(e)})).map((function(e){if(e.sourceId,r.includes(e.sourceId))throw new Error("[Autocomplete] The `sourceId` ".concat(JSON.stringify(e.sourceId)," is not unique."));r.push(e.sourceId);var t={getItemInputValue:function(e){return e.state.query},getItemUrl:function(){},onSelect:function(e){(0,e.setIsOpen)(!1)},onActive:a,onResolve:a};Object.keys(t).forEach((function(e){t[e].__default=!0}));var n=$($({},t),e);return Promise.resolve(n)})))}))}(e,r)}))).then((function(e){return L(e)})).then((function(e){return e.map((function(e){return X(X({},e),{},{onSelect:function(r){e.onSelect(r),t.forEach((function(e){var t;return null===(t=e.onSelect)||void 0===t?void 0:t.call(e,r)}))},onActive:function(r){e.onActive(r),t.forEach((function(e){var t;return null===(t=e.onActive)||void 0===t?void 0:t.call(e,r)}))},onResolve:function(r){e.onResolve(r),t.forEach((function(e){var t;return null===(t=e.onResolve)||void 0===t?void 0:t.call(e,r)}))}})}))}))},navigator:X({navigate:function(e){var t=e.itemUrl;n.location.assign(t)},navigateNewTab:function(e){var t=e.itemUrl,r=n.open(t,"_blank","noopener");null==r||r.focus()},navigateNewWindow:function(e){var t=e.itemUrl;n.open(t,"_blank","noopener")}},e.navigator)})}function te(e){return te="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},te(e)}function re(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function ne(e){for(var t=1;te.length)&&(t=e.length);for(var r=0,n=new Array(t);r=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var Ie,De,Ae,ke=null,xe=(Ie=-1,De=-1,Ae=void 0,function(e){var t=++Ie;return Promise.resolve(e).then((function(e){return Ae&&t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var Me=/((gt|sm)-|galaxy nexus)|samsung[- ]|samsungbrowser/i;function He(e){return He="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},He(e)}var Fe=["props","refresh","store"],Ue=["inputElement","formElement","panelElement"],Be=["inputElement"],Ve=["inputElement","maxLength"],Ke=["sourceIndex"],$e=["sourceIndex"],Je=["item","source","sourceIndex"];function ze(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function We(e){for(var t=1;t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function Ge(e){var t=e.props,r=e.refresh,n=e.store,o=Ze(e,Fe),i=function(e,t){return void 0!==t?"".concat(e,"-").concat(t):e};return{getEnvironmentProps:function(e){var r=e.inputElement,o=e.formElement,i=e.panelElement;function a(e){!n.getState().isOpen&&n.pendingRequests.isEmpty()||e.target===r||!1===[o,i].some((function(t){return r=t,n=e.target,r===n||r.contains(n);var r,n}))&&(n.dispatch("blur",null),t.debug||n.pendingRequests.cancelAll())}return We({onTouchStart:a,onMouseDown:a,onTouchMove:function(e){!1!==n.getState().isOpen&&r===t.environment.document.activeElement&&e.target!==r&&r.blur()}},Ze(e,Ue))},getRootProps:function(e){return We({role:"combobox","aria-expanded":n.getState().isOpen,"aria-haspopup":"listbox","aria-owns":n.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label")},e)},getFormProps:function(e){e.inputElement;return We({action:"",noValidate:!0,role:"search",onSubmit:function(i){var a;i.preventDefault(),t.onSubmit(We({event:i,refresh:r,state:n.getState()},o)),n.dispatch("submit",null),null===(a=e.inputElement)||void 0===a||a.blur()},onReset:function(i){var a;i.preventDefault(),t.onReset(We({event:i,refresh:r,state:n.getState()},o)),n.dispatch("reset",null),null===(a=e.inputElement)||void 0===a||a.focus()}},Ze(e,Be))},getLabelProps:function(e){var r=e||{},n=r.sourceIndex,o=Ze(r,Ke);return We({htmlFor:"".concat(i(t.id,n),"-input"),id:"".concat(i(t.id,n),"-label")},o)},getInputProps:function(e){var i;function c(e){(t.openOnFocus||Boolean(n.getState().query))&&Ce(We({event:e,props:t,query:n.getState().completion||n.getState().query,refresh:r,store:n},o)),n.dispatch("focus",null)}var l=e||{},u=(l.inputElement,l.maxLength),s=void 0===u?512:u,f=Ze(l,Ve),m=ge(n.getState()),p=function(e){return Boolean(e&&e.match(Me))}((null===(i=t.environment.navigator)||void 0===i?void 0:i.userAgent)||""),v=null!=m&&m.itemUrl&&!p?"go":"search";return We({"aria-autocomplete":"both","aria-activedescendant":n.getState().isOpen&&null!==n.getState().activeItemId?"".concat(t.id,"-item-").concat(n.getState().activeItemId):void 0,"aria-controls":n.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label"),value:n.getState().completion||n.getState().query,id:"".concat(t.id,"-input"),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",enterKeyHint:v,spellCheck:"false",autoFocus:t.autoFocus,placeholder:t.placeholder,maxLength:s,type:"search",onChange:function(e){Ce(We({event:e,props:t,query:e.currentTarget.value.slice(0,s),refresh:r,store:n},o))},onKeyDown:function(e){!function(e){var t=e.event,r=e.props,n=e.refresh,o=e.store,i=Le(e,_e);if("ArrowUp"===t.key||"ArrowDown"===t.key){var a=function(){var e=r.environment.document.getElementById("".concat(r.id,"-item-").concat(o.getState().activeItemId));e&&(e.scrollIntoViewIfNeeded?e.scrollIntoViewIfNeeded(!1):e.scrollIntoView(!1))},c=function(){var e=ge(o.getState());if(null!==o.getState().activeItemId&&e){var r=e.item,a=e.itemInputValue,c=e.itemUrl,l=e.source;l.onActive(qe({event:t,item:r,itemInputValue:a,itemUrl:c,refresh:n,source:l,state:o.getState()},i))}};t.preventDefault(),!1===o.getState().isOpen&&(r.openOnFocus||Boolean(o.getState().query))?Ce(qe({event:t,props:r,query:o.getState().query,refresh:n,store:o},i)).then((function(){o.dispatch(t.key,{nextActiveItemId:r.defaultActiveItemId}),c(),setTimeout(a,0)})):(o.dispatch(t.key,{}),c(),a())}else if("Escape"===t.key)t.preventDefault(),o.dispatch(t.key,null),o.pendingRequests.cancelAll();else if("Tab"===t.key)o.dispatch("blur",null),o.pendingRequests.cancelAll();else if("Enter"===t.key){if(null===o.getState().activeItemId||o.getState().collections.every((function(e){return 0===e.items.length})))return void(r.debug||o.pendingRequests.cancelAll());t.preventDefault();var l=ge(o.getState()),u=l.item,s=l.itemInputValue,f=l.itemUrl,m=l.source;if(t.metaKey||t.ctrlKey)void 0!==f&&(m.onSelect(qe({event:t,item:u,itemInputValue:s,itemUrl:f,refresh:n,source:m,state:o.getState()},i)),r.navigator.navigateNewTab({itemUrl:f,item:u,state:o.getState()}));else if(t.shiftKey)void 0!==f&&(m.onSelect(qe({event:t,item:u,itemInputValue:s,itemUrl:f,refresh:n,source:m,state:o.getState()},i)),r.navigator.navigateNewWindow({itemUrl:f,item:u,state:o.getState()}));else if(t.altKey);else{if(void 0!==f)return m.onSelect(qe({event:t,item:u,itemInputValue:s,itemUrl:f,refresh:n,source:m,state:o.getState()},i)),void r.navigator.navigate({itemUrl:f,item:u,state:o.getState()});Ce(qe({event:t,nextState:{isOpen:!1},props:r,query:s,refresh:n,store:o},i)).then((function(){m.onSelect(qe({event:t,item:u,itemInputValue:s,itemUrl:f,refresh:n,source:m,state:o.getState()},i))}))}}}(We({event:e,props:t,refresh:r,store:n},o))},onFocus:c,onBlur:a,onClick:function(r){e.inputElement!==t.environment.document.activeElement||n.getState().isOpen||c(r)}},f)},getPanelProps:function(e){return We({onMouseDown:function(e){e.preventDefault()},onMouseLeave:function(){n.dispatch("mouseleave",null)}},e)},getListProps:function(e){var r=e||{},n=r.sourceIndex,o=Ze(r,$e);return We({role:"listbox","aria-labelledby":"".concat(i(t.id,n),"-label"),id:"".concat(i(t.id,n),"-list")},o)},getItemProps:function(e){var a=e.item,c=e.source,l=e.sourceIndex,u=Ze(e,Je);return We({id:"".concat(i(t.id,l),"-item-").concat(a.__autocomplete_id),role:"option","aria-selected":n.getState().activeItemId===a.__autocomplete_id,onMouseMove:function(e){if(a.__autocomplete_id!==n.getState().activeItemId){n.dispatch("mousemove",a.__autocomplete_id);var t=ge(n.getState());if(null!==n.getState().activeItemId&&t){var i=t.item,c=t.itemInputValue,l=t.itemUrl,u=t.source;u.onActive(We({event:e,item:i,itemInputValue:c,itemUrl:l,refresh:r,source:u,state:n.getState()},o))}}},onMouseDown:function(e){e.preventDefault()},onClick:function(e){var i=c.getItemInputValue({item:a,state:n.getState()}),l=c.getItemUrl({item:a,state:n.getState()});(l?Promise.resolve():Ce(We({event:e,nextState:{isOpen:!1},props:t,query:i,refresh:r,store:n},o))).then((function(){c.onSelect(We({event:e,item:a,itemInputValue:i,itemUrl:l,refresh:r,source:c,state:n.getState()},o))}))}},u)}}}var Xe=[{segment:"autocomplete-core",version:"1.9.3"}];function Ye(e){return Ye="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Ye(e)}function et(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function tt(e){for(var t=1;t=r?null===n?null:0:o}function at(e){return at="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},at(e)}function ct(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function lt(e){for(var t=1;te.length)&&(t=e.length);for(var r=0,n=new Array(t);r=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function kt(e){var t=e.translations,r=void 0===t?{}:t,n=At(e,Pt),o=r.noResultsText,i=void 0===o?"No results for":o,a=r.suggestedQueryText,c=void 0===a?"Try searching for":a,l=r.reportMissingResultsText,u=void 0===l?"Believe this query should return results?":l,s=r.reportMissingResultsLinkText,f=void 0===s?"Let us know.":s,m=n.state.context.searchSuggestions;return yt.createElement("div",{className:"DocSearch-NoResults"},yt.createElement("div",{className:"DocSearch-Screen-Icon"},yt.createElement(Et,null)),yt.createElement("p",{className:"DocSearch-Title"},i,' "',yt.createElement("strong",null,n.state.query),'"'),m&&m.length>0&&yt.createElement("div",{className:"DocSearch-NoResults-Prefill-List"},yt.createElement("p",{className:"DocSearch-Help"},c,":"),yt.createElement("ul",null,m.slice(0,3).reduce((function(e,t){return[].concat(It(e),[yt.createElement("li",{key:t},yt.createElement("button",{className:"DocSearch-Prefill",key:t,type:"button",onClick:function(){n.setQuery(t.toLowerCase()+" "),n.refresh(),n.inputRef.current.focus()}},t))])}),[]))),n.getMissingResultsUrl&&yt.createElement("p",{className:"DocSearch-Help"},"".concat(u," "),yt.createElement("a",{href:n.getMissingResultsUrl({query:n.state.query}),target:"_blank",rel:"noopener noreferrer"},f)))}var xt=function(){return yt.createElement("svg",{width:"20",height:"20",viewBox:"0 0 20 20"},yt.createElement("path",{d:"M17 6v12c0 .52-.2 1-1 1H4c-.7 0-1-.33-1-1V2c0-.55.42-1 1-1h8l5 5zM14 8h-3.13c-.51 0-.87-.34-.87-.87V4",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinejoin:"round"}))};function Ct(e){switch(e.type){case"lvl1":return yt.createElement(xt,null);case"content":return yt.createElement(_t,null);default:return yt.createElement(Nt,null)}}function Nt(){return yt.createElement("svg",{width:"20",height:"20",viewBox:"0 0 20 20"},yt.createElement("path",{d:"M13 13h4-4V8H7v5h6v4-4H7V8H3h4V3v5h6V3v5h4-4v5zm-6 0v4-4H3h4z",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"}))}function _t(){return yt.createElement("svg",{width:"20",height:"20",viewBox:"0 0 20 20"},yt.createElement("path",{d:"M17 5H3h14zm0 5H3h14zm0 5H3h14z",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinejoin:"round"}))}function Tt(){return yt.createElement("svg",{className:"DocSearch-Hit-Select-Icon",width:"20",height:"20",viewBox:"0 0 20 20"},yt.createElement("g",{stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"},yt.createElement("path",{d:"M18 3v4c0 2-2 4-4 4H2"}),yt.createElement("path",{d:"M8 17l-6-6 6-6"})))}var qt=["hit","attribute","tagName"];function Rt(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function Lt(e){for(var t=1;t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function Ft(e,t){return t.split(".").reduce((function(e,t){return null!=e&&e[t]?e[t]:null}),e)}function Ut(e){var t=e.hit,r=e.attribute,n=e.tagName,o=void 0===n?"span":n,i=Ht(e,qt);return(0,yt.createElement)(o,Lt(Lt({},i),{},{dangerouslySetInnerHTML:{__html:Ft(t,"_snippetResult.".concat(r,".value"))||Ft(t,r)}}))}function Bt(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var r=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null==r)return;var n,o,i=[],a=!0,c=!1;try{for(r=r.call(e);!(a=(n=r.next()).done)&&(i.push(n.value),!t||i.length!==t);a=!0);}catch(l){c=!0,o=l}finally{try{a||null==r.return||r.return()}finally{if(c)throw o}}return i}(e,t)||function(e,t){if(!e)return;if("string"==typeof e)return Vt(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);"Object"===r&&e.constructor&&(r=e.constructor.name);if("Map"===r||"Set"===r)return Array.from(e);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return Vt(e,t)}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Vt(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r|<\/mark>)/g,Wt=RegExp(zt.source);function Qt(e){var t,r,n=e;if(!n.__docsearch_parent&&!e._highlightResult)return e.hierarchy.lvl0;var o=((n.__docsearch_parent?null===(t=n.__docsearch_parent)||void 0===t||null===(t=t._highlightResult)||void 0===t||null===(t=t.hierarchy)||void 0===t?void 0:t.lvl0:null===(r=e._highlightResult)||void 0===r||null===(r=r.hierarchy)||void 0===r?void 0:r.lvl0)||{}).value;return o&&Wt.test(o)?o.replace(zt,""):o}function Zt(){return Zt=Object.assign||function(e){for(var t=1;t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function or(e){var t=e.translations,r=void 0===t?{}:t,n=nr(e,tr),o=r.recentSearchesTitle,i=void 0===o?"Recent":o,a=r.noRecentSearchesText,c=void 0===a?"No recent searches":a,l=r.saveRecentSearchButtonTitle,u=void 0===l?"Save this search":l,s=r.removeRecentSearchButtonTitle,f=void 0===s?"Remove this search from history":s,m=r.favoriteSearchesTitle,p=void 0===m?"Favorite":m,v=r.removeFavoriteSearchButtonTitle,d=void 0===v?"Remove this search from favorites":v;return"idle"===n.state.status&&!1===n.hasCollections?n.disableUserPersonalization?null:yt.createElement("div",{className:"DocSearch-StartScreen"},yt.createElement("p",{className:"DocSearch-Help"},c)):!1===n.hasCollections?null:yt.createElement("div",{className:"DocSearch-Dropdown-Container"},yt.createElement($t,rr({},n,{title:i,collection:n.state.collections[0],renderIcon:function(){return yt.createElement("div",{className:"DocSearch-Hit-icon"},yt.createElement(Xt,null))},renderAction:function(e){var t=e.item,r=e.runFavoriteTransition,o=e.runDeleteTransition;return yt.createElement(yt.Fragment,null,yt.createElement("div",{className:"DocSearch-Hit-action"},yt.createElement("button",{className:"DocSearch-Hit-action-button",title:u,type:"submit",onClick:function(e){e.preventDefault(),e.stopPropagation(),r((function(){n.favoriteSearches.add(t),n.recentSearches.remove(t),n.refresh()}))}},yt.createElement(Yt,null))),yt.createElement("div",{className:"DocSearch-Hit-action"},yt.createElement("button",{className:"DocSearch-Hit-action-button",title:f,type:"submit",onClick:function(e){e.preventDefault(),e.stopPropagation(),o((function(){n.recentSearches.remove(t),n.refresh()}))}},yt.createElement(er,null))))}})),yt.createElement($t,rr({},n,{title:p,collection:n.state.collections[1],renderIcon:function(){return yt.createElement("div",{className:"DocSearch-Hit-icon"},yt.createElement(Yt,null))},renderAction:function(e){var t=e.item,r=e.runDeleteTransition;return yt.createElement("div",{className:"DocSearch-Hit-action"},yt.createElement("button",{className:"DocSearch-Hit-action-button",title:d,type:"submit",onClick:function(e){e.preventDefault(),e.stopPropagation(),r((function(){n.favoriteSearches.remove(t),n.refresh()}))}},yt.createElement(er,null)))}})))}var ir=["translations"];function ar(){return ar=Object.assign||function(e){for(var t=1;t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var lr=yt.memo((function(e){var t=e.translations,r=void 0===t?{}:t,n=cr(e,ir);if("error"===n.state.status)return yt.createElement(wt,{translations:null==r?void 0:r.errorScreen});var o=n.state.collections.some((function(e){return e.items.length>0}));return n.state.query?!1===o?yt.createElement(kt,ar({},n,{translations:null==r?void 0:r.noResultsScreen})):yt.createElement(Gt,n):yt.createElement(or,ar({},n,{hasCollections:o,translations:null==r?void 0:r.startScreen}))}),(function(e,t){return"loading"===t.state.status||"stalled"===t.state.status}));function ur(){return yt.createElement("svg",{viewBox:"0 0 38 38",stroke:"currentColor",strokeOpacity:".5"},yt.createElement("g",{fill:"none",fillRule:"evenodd"},yt.createElement("g",{transform:"translate(1 1)",strokeWidth:"2"},yt.createElement("circle",{strokeOpacity:".3",cx:"18",cy:"18",r:"18"}),yt.createElement("path",{d:"M36 18c0-9.94-8.06-18-18-18"},yt.createElement("animateTransform",{attributeName:"transform",type:"rotate",from:"0 18 18",to:"360 18 18",dur:"1s",repeatCount:"indefinite"})))))}var sr=r(89188),fr=["translations"];function mr(){return mr=Object.assign||function(e){for(var t=1;t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function vr(e){var t=e.translations,r=void 0===t?{}:t,n=pr(e,fr),o=r.resetButtonTitle,i=void 0===o?"Clear the query":o,a=r.resetButtonAriaLabel,c=void 0===a?"Clear the query":a,l=r.cancelButtonText,u=void 0===l?"Cancel":l,s=r.cancelButtonAriaLabel,f=void 0===s?"Cancel":s,m=r.searchInputLabel,p=void 0===m?"Search":m,v=n.getFormProps({inputElement:n.inputRef.current}).onReset;return yt.useEffect((function(){n.autoFocus&&n.inputRef.current&&n.inputRef.current.focus()}),[n.autoFocus,n.inputRef]),yt.useEffect((function(){n.isFromSelection&&n.inputRef.current&&n.inputRef.current.select()}),[n.isFromSelection,n.inputRef]),yt.createElement(yt.Fragment,null,yt.createElement("form",{className:"DocSearch-Form",onSubmit:function(e){e.preventDefault()},onReset:v},yt.createElement("label",mr({className:"DocSearch-MagnifierLabel"},n.getLabelProps()),yt.createElement(sr.W,null),yt.createElement("span",{className:"DocSearch-VisuallyHiddenForAccessibility"},p)),yt.createElement("div",{className:"DocSearch-LoadingIndicator"},yt.createElement(ur,null)),yt.createElement("input",mr({className:"DocSearch-Input",ref:n.inputRef},n.getInputProps({inputElement:n.inputRef.current,autoFocus:n.autoFocus,maxLength:ht}))),yt.createElement("button",{type:"reset",title:i,className:"DocSearch-Reset","aria-label":c,hidden:!n.state.query},yt.createElement(er,null))),yt.createElement("button",{className:"DocSearch-Cancel",type:"reset","aria-label":f,onClick:n.onClose},u))}var dr=["_highlightResult","_snippetResult"];function yr(e,t){if(null==e)return{};var r,n,o=function(e,t){if(null==e)return{};var r,n,o={},i=Object.keys(e);for(n=0;n=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function hr(e){return!1===function(){var e="__TEST_KEY__";try{return localStorage.setItem(e,""),localStorage.removeItem(e),!0}catch(t){return!1}}()?{setItem:function(){},getItem:function(){return[]}}:{setItem:function(t){return window.localStorage.setItem(e,JSON.stringify(t))},getItem:function(){var t=window.localStorage.getItem(e);return t?JSON.parse(t):[]}}}function br(e){var t=e.key,r=e.limit,n=void 0===r?5:r,o=hr(t),i=o.getItem().slice(0,n);return{add:function(e){var t=e,r=(t._highlightResult,t._snippetResult,yr(t,dr)),a=i.findIndex((function(e){return e.objectID===r.objectID}));a>-1&&i.splice(a,1),i.unshift(r),i=i.slice(0,n),o.setItem(i)},remove:function(e){i=i.filter((function(t){return t.objectID!==e.objectID})),o.setItem(i)},getAll:function(){return i}}}function gr(e){const t=`algoliasearch-client-js-${e.key}`;let r;const n=()=>(void 0===r&&(r=e.localStorage||window.localStorage),r),o=()=>JSON.parse(n().getItem(t)||"{}"),i=e=>{n().setItem(t,JSON.stringify(e))};return{get:(t,r,n={miss:()=>Promise.resolve()})=>Promise.resolve().then((()=>{(()=>{const t=e.timeToLive?1e3*e.timeToLive:null,r=o(),n=Object.fromEntries(Object.entries(r).filter((([,e])=>void 0!==e.timestamp)));if(i(n),!t)return;const a=Object.fromEntries(Object.entries(n).filter((([,e])=>{const r=(new Date).getTime();return!(e.timestamp+tPromise.all([e?e.value:r(),void 0!==e]))).then((([e,t])=>Promise.all([e,t||n.miss(e)]))).then((([e])=>e)),set:(e,r)=>Promise.resolve().then((()=>{const i=o();return i[JSON.stringify(e)]={timestamp:(new Date).getTime(),value:r},n().setItem(t,JSON.stringify(i)),r})),delete:e=>Promise.resolve().then((()=>{const r=o();delete r[JSON.stringify(e)],n().setItem(t,JSON.stringify(r))})),clear:()=>Promise.resolve().then((()=>{n().removeItem(t)}))}}function Or(e){const t=[...e.caches],r=t.shift();return void 0===r?{get:(e,t,r={miss:()=>Promise.resolve()})=>t().then((e=>Promise.all([e,r.miss(e)]))).then((([e])=>e)),set:(e,t)=>Promise.resolve(t),delete:e=>Promise.resolve(),clear:()=>Promise.resolve()}:{get:(e,n,o={miss:()=>Promise.resolve()})=>r.get(e,n,o).catch((()=>Or({caches:t}).get(e,n,o))),set:(e,n)=>r.set(e,n).catch((()=>Or({caches:t}).set(e,n))),delete:e=>r.delete(e).catch((()=>Or({caches:t}).delete(e))),clear:()=>r.clear().catch((()=>Or({caches:t}).clear()))}}function Sr(e={serializable:!0}){let t={};return{get(r,n,o={miss:()=>Promise.resolve()}){const i=JSON.stringify(r);if(i in t)return Promise.resolve(e.serializable?JSON.parse(t[i]):t[i]);const a=n(),c=o&&o.miss||(()=>Promise.resolve());return a.then((e=>c(e))).then((()=>a))},set:(r,n)=>(t[JSON.stringify(r)]=e.serializable?JSON.stringify(n):n,Promise.resolve(n)),delete:e=>(delete t[JSON.stringify(e)],Promise.resolve()),clear:()=>(t={},Promise.resolve())}}function jr(e){let t=e.length-1;for(;t>0;t--){const r=Math.floor(Math.random()*(t+1)),n=e[t];e[t]=e[r],e[r]=n}return e}function wr(e,t){return t?(Object.keys(t).forEach((r=>{e[r]=t[r](e)})),e):e}function Er(e,...t){let r=0;return e.replace(/%s/g,(()=>encodeURIComponent(t[r++])))}const Pr="4.23.3",Ir={WithinQueryParameters:0,WithinHeaders:1};function Dr(e,t){const r=e||{},n=r.data||{};return Object.keys(r).forEach((e=>{-1===["timeout","headers","queryParameters","data","cacheable"].indexOf(e)&&(n[e]=r[e])})),{data:Object.entries(n).length>0?n:void 0,timeout:r.timeout||t,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}const Ar={Read:1,Write:2,Any:3},kr={Up:1,Down:2,Timeouted:3},xr=12e4;function Cr(e,t=kr.Up){return{...e,status:t,lastUpdate:Date.now()}}function Nr(e){return"string"==typeof e?{protocol:"https",url:e,accept:Ar.Any}:{protocol:e.protocol||"https",url:e.url,accept:e.accept||Ar.Any}}const _r={Delete:"DELETE",Get:"GET",Post:"POST",Put:"PUT"};function Tr(e,t){return Promise.all(t.map((t=>e.get(t,(()=>Promise.resolve(Cr(t))))))).then((e=>{const r=e.filter((e=>function(e){return e.status===kr.Up||Date.now()-e.lastUpdate>xr}(e))),n=e.filter((e=>function(e){return e.status===kr.Timeouted&&Date.now()-e.lastUpdate<=xr}(e))),o=[...r,...n];return{getTimeout:(e,t)=>(0===n.length&&0===e?1:n.length+3+e)*t,statelessHosts:o.length>0?o.map((e=>Nr(e))):t}}))}const qr=(e,t)=>(e=>{const t=e.status;return e.isTimedOut||(({isTimedOut:e,status:t})=>!e&&!~~t)(e)||2!=~~(t/100)&&4!=~~(t/100)})(e)?t.onRetry(e):(({status:e})=>2==~~(e/100))(e)?t.onSuccess(e):t.onFail(e);function Rr(e,t,r,n){const o=[],i=function(e,t){if(e.method===_r.Get||void 0===e.data&&void 0===t.data)return;const r=Array.isArray(e.data)?e.data:{...e.data,...t.data};return JSON.stringify(r)}(r,n),a=function(e,t){const r={...e.headers,...t.headers},n={};return Object.keys(r).forEach((e=>{const t=r[e];n[e.toLowerCase()]=t})),n}(e,n),c=r.method,l=r.method!==_r.Get?{}:{...r.data,...n.data},u={"x-algolia-agent":e.userAgent.value,...e.queryParameters,...l,...n.queryParameters};let s=0;const f=(t,l)=>{const m=t.pop();if(void 0===m)throw{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:Fr(o)};const p={data:i,headers:a,method:c,url:Mr(m,r.path,u),connectTimeout:l(s,e.timeouts.connect),responseTimeout:l(s,n.timeout)},v=e=>{const r={request:p,response:e,host:m,triesLeft:t.length};return o.push(r),r},d={onSuccess:e=>function(e){try{return JSON.parse(e.content)}catch(t){throw function(e,t){return{name:"DeserializationError",message:e,response:t}}(t.message,e)}}(e),onRetry(r){const n=v(r);return r.isTimedOut&&s++,Promise.all([e.logger.info("Retryable failure",Ur(n)),e.hostsCache.set(m,Cr(m,r.isTimedOut?kr.Timeouted:kr.Down))]).then((()=>f(t,l)))},onFail(e){throw v(e),function({content:e,status:t},r){let n=e;try{n=JSON.parse(e).message}catch(o){}return function(e,t,r){return{name:"ApiError",message:e,status:t,transporterStackTrace:r}}(n,t,r)}(e,Fr(o))}};return e.requester.send(p).then((e=>qr(e,d)))};return Tr(e.hostsCache,t).then((e=>f([...e.statelessHosts].reverse(),e.getTimeout)))}function Lr(e){const t={value:`Algolia for JavaScript (${e})`,add(e){const r=`; ${e.segment}${void 0!==e.version?` (${e.version})`:""}`;return-1===t.value.indexOf(r)&&(t.value=`${t.value}${r}`),t}};return t}function Mr(e,t,r){const n=Hr(r);let o=`${e.protocol}://${e.url}/${"/"===t.charAt(0)?t.substr(1):t}`;return n.length&&(o+=`?${n}`),o}function Hr(e){return Object.keys(e).map((t=>{return Er("%s=%s",t,(r=e[t],"[object Object]"===Object.prototype.toString.call(r)||"[object Array]"===Object.prototype.toString.call(r)?JSON.stringify(e[t]):e[t]));var r})).join("&")}function Fr(e){return e.map((e=>Ur(e)))}function Ur(e){const t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return{...e,request:{...e.request,headers:{...e.request.headers,...t}}}}const Br=e=>{const t=e.appId,r=function(e,t,r){const n={"x-algolia-api-key":r,"x-algolia-application-id":t};return{headers:()=>e===Ir.WithinHeaders?n:{},queryParameters:()=>e===Ir.WithinQueryParameters?n:{}}}(void 0!==e.authMode?e.authMode:Ir.WithinHeaders,t,e.apiKey),n=function(e){const{hostsCache:t,logger:r,requester:n,requestsCache:o,responsesCache:i,timeouts:a,userAgent:c,hosts:l,queryParameters:u,headers:s}=e,f={hostsCache:t,logger:r,requester:n,requestsCache:o,responsesCache:i,timeouts:a,userAgent:c,headers:s,queryParameters:u,hosts:l.map((e=>Nr(e))),read(e,t){const r=Dr(t,f.timeouts.read),n=()=>Rr(f,f.hosts.filter((e=>!!(e.accept&Ar.Read))),e,r);if(!0!==(void 0!==r.cacheable?r.cacheable:e.cacheable))return n();const o={request:e,mappedRequestOptions:r,transporter:{queryParameters:f.queryParameters,headers:f.headers}};return f.responsesCache.get(o,(()=>f.requestsCache.get(o,(()=>f.requestsCache.set(o,n()).then((e=>Promise.all([f.requestsCache.delete(o),e])),(e=>Promise.all([f.requestsCache.delete(o),Promise.reject(e)]))).then((([e,t])=>t))))),{miss:e=>f.responsesCache.set(o,e)})},write:(e,t)=>Rr(f,f.hosts.filter((e=>!!(e.accept&Ar.Write))),e,Dr(t,f.timeouts.write))};return f}({hosts:[{url:`${t}-dsn.algolia.net`,accept:Ar.Read},{url:`${t}.algolia.net`,accept:Ar.Write}].concat(jr([{url:`${t}-1.algolianet.com`},{url:`${t}-2.algolianet.com`},{url:`${t}-3.algolianet.com`}])),...e,headers:{...r.headers(),"content-type":"application/x-www-form-urlencoded",...e.headers},queryParameters:{...r.queryParameters(),...e.queryParameters}}),o={transporter:n,appId:t,addAlgoliaAgent(e,t){n.userAgent.add({segment:e,version:t})},clearCache:()=>Promise.all([n.requestsCache.clear(),n.responsesCache.clear()]).then((()=>{}))};return wr(o,e.methods)},Vr=e=>(t,r)=>t.method===_r.Get?e.transporter.read(t,r):e.transporter.write(t,r),Kr=e=>(t,r={})=>wr({transporter:e.transporter,appId:e.appId,indexName:t},r.methods),$r=e=>(t,r)=>{const n=t.map((e=>({...e,params:Hr(e.params||{})})));return e.transporter.read({method:_r.Post,path:"1/indexes/*/queries",data:{requests:n},cacheable:!0},r)},Jr=e=>(t,r)=>Promise.all(t.map((t=>{const{facetName:n,facetQuery:o,...i}=t.params;return Kr(e)(t.indexName,{methods:{searchForFacetValues:Qr}}).searchForFacetValues(n,o,{...r,...i})}))),zr=e=>(t,r,n)=>e.transporter.read({method:_r.Post,path:Er("1/answers/%s/prediction",e.indexName),data:{query:t,queryLanguages:r},cacheable:!0},n),Wr=e=>(t,r)=>e.transporter.read({method:_r.Post,path:Er("1/indexes/%s/query",e.indexName),data:{query:t},cacheable:!0},r),Qr=e=>(t,r,n)=>e.transporter.read({method:_r.Post,path:Er("1/indexes/%s/facets/%s/query",e.indexName,t),data:{facetQuery:r},cacheable:!0},n),Zr={Debug:1,Info:2,Error:3};const Gr=e=>(t,r)=>{const n=t.map((e=>({...e,threshold:e.threshold||0})));return e.transporter.read({method:_r.Post,path:"1/indexes/*/recommendations",data:{requests:n},cacheable:!0},r)};function Xr(e,t,r){const n={appId:e,apiKey:t,timeouts:{connect:1,read:2,write:30},requester:{send:e=>new Promise((t=>{const r=new XMLHttpRequest;r.open(e.method,e.url,!0),Object.keys(e.headers).forEach((t=>r.setRequestHeader(t,e.headers[t])));const n=(e,n)=>setTimeout((()=>{r.abort(),t({status:0,content:n,isTimedOut:!0})}),1e3*e),o=n(e.connectTimeout,"Connection timeout");let i;r.onreadystatechange=()=>{r.readyState>r.OPENED&&void 0===i&&(clearTimeout(o),i=n(e.responseTimeout,"Socket timeout"))},r.onerror=()=>{0===r.status&&(clearTimeout(o),clearTimeout(i),t({content:r.responseText||"Network request failed",status:r.status,isTimedOut:!1}))},r.onload=()=>{clearTimeout(o),clearTimeout(i),t({content:r.responseText,status:r.status,isTimedOut:!1})},r.send(e.data)}))},logger:(o=Zr.Error,{debug:(e,t)=>(Zr.Debug>=o&&console.debug(e,t),Promise.resolve()),info:(e,t)=>(Zr.Info>=o&&console.info(e,t),Promise.resolve()),error:(e,t)=>(console.error(e,t),Promise.resolve())}),responsesCache:Sr(),requestsCache:Sr({serializable:!1}),hostsCache:Or({caches:[gr({key:`${Pr}-${e}`}),Sr()]}),userAgent:Lr(Pr).add({segment:"Browser",version:"lite"}),authMode:Ir.WithinQueryParameters};var o;return Br({...n,...r,methods:{search:$r,searchForFacetValues:Jr,multipleQueries:$r,multipleSearchForFacetValues:Jr,customRequest:Vr,initIndex:e=>t=>Kr(e)(t,{methods:{search:Wr,searchForFacetValues:Qr,findAnswers:zr}}),getRecommendations:Gr}})}Xr.version=Pr;const Yr=Xr;var en="3.6.0";function tn(){}function rn(e){return e}function nn(e){return 1===e.button||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey}function on(e,t,r){return e.reduce((function(e,n){var o=t(n);return e.hasOwnProperty(o)||(e[o]=[]),e[o].length<(r||5)&&e[o].push(n),e}),{})}var an=["footer","searchBox"];function cn(){return cn=Object.assign||function(e){for(var t=1;te.length)&&(t=e.length);for(var r=0,n=new Array(t);r=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function vn(e){var t=e.appId,r=e.apiKey,n=e.indexName,o=e.placeholder,i=void 0===o?"Search docs":o,a=e.searchParameters,c=e.maxResultsPerGroup,l=e.onClose,u=void 0===l?tn:l,s=e.transformItems,f=void 0===s?rn:s,m=e.hitComponent,p=void 0===m?St:m,v=e.resultsFooterComponent,d=void 0===v?function(){return null}:v,y=e.navigator,h=e.initialScrollY,b=void 0===h?0:h,g=e.transformSearchClient,O=void 0===g?rn:g,S=e.disableUserPersonalization,j=void 0!==S&&S,w=e.initialQuery,E=void 0===w?"":w,P=e.translations,I=void 0===P?{}:P,D=e.getMissingResultsUrl,A=e.insights,k=void 0!==A&&A,x=I.footer,C=I.searchBox,N=pn(I,an),_=fn(yt.useState({query:"",collections:[],completion:null,context:{},isOpen:!1,activeItemId:null,status:"idle"}),2),T=_[0],q=_[1],R=yt.useRef(null),L=yt.useRef(null),M=yt.useRef(null),H=yt.useRef(null),F=yt.useRef(null),U=yt.useRef(10),B=yt.useRef("undefined"!=typeof window?window.getSelection().toString().slice(0,ht):"").current,V=yt.useRef(E||B).current,K=function(e,t,r){return yt.useMemo((function(){var n=Yr(e,t);return n.addAlgoliaAgent("docsearch",en),!1===/docsearch.js \(.*\)/.test(n.transporter.userAgent.value)&&n.addAlgoliaAgent("docsearch-react",en),r(n)}),[e,t,r])}(t,r,O),$=yt.useRef(br({key:"__DOCSEARCH_FAVORITE_SEARCHES__".concat(n),limit:10})).current,J=yt.useRef(br({key:"__DOCSEARCH_RECENT_SEARCHES__".concat(n),limit:0===$.getAll().length?7:4})).current,z=yt.useCallback((function(e){if(!j){var t="content"===e.type?e.__docsearch_parent:e;t&&-1===$.getAll().findIndex((function(e){return e.objectID===t.objectID}))&&J.add(t)}}),[$,J,j]),W=yt.useCallback((function(e){if(T.context.algoliaInsightsPlugin&&e.__autocomplete_id){var t=e,r={eventName:"Item Selected",index:t.__autocomplete_indexName,items:[t],positions:[e.__autocomplete_id],queryID:t.__autocomplete_queryID};T.context.algoliaInsightsPlugin.insights.clickedObjectIDsAfterSearch(r)}}),[T.context.algoliaInsightsPlugin]),Q=yt.useMemo((function(){return dt({id:"docsearch",defaultActiveItemId:0,placeholder:i,openOnFocus:!0,initialState:{query:V,context:{searchSuggestions:[]}},insights:k,navigator:y,onStateChange:function(e){q(e.state)},getSources:function(e){var o=e.query,i=e.state,l=e.setContext,s=e.setStatus;if(!o)return j?[]:[{sourceId:"recentSearches",onSelect:function(e){var t=e.item,r=e.event;z(t),nn(r)||u()},getItemUrl:function(e){return e.item.url},getItems:function(){return J.getAll()}},{sourceId:"favoriteSearches",onSelect:function(e){var t=e.item,r=e.event;z(t),nn(r)||u()},getItemUrl:function(e){return e.item.url},getItems:function(){return $.getAll()}}];var m=Boolean(k);return K.search([{query:o,indexName:n,params:un({attributesToRetrieve:["hierarchy.lvl0","hierarchy.lvl1","hierarchy.lvl2","hierarchy.lvl3","hierarchy.lvl4","hierarchy.lvl5","hierarchy.lvl6","content","type","url"],attributesToSnippet:["hierarchy.lvl1:".concat(U.current),"hierarchy.lvl2:".concat(U.current),"hierarchy.lvl3:".concat(U.current),"hierarchy.lvl4:".concat(U.current),"hierarchy.lvl5:".concat(U.current),"hierarchy.lvl6:".concat(U.current),"content:".concat(U.current)],snippetEllipsisText:"\u2026",highlightPreTag:"",highlightPostTag:"",hitsPerPage:20,clickAnalytics:m},a)}]).catch((function(e){throw"RetryError"===e.name&&s("error"),e})).then((function(e){var o=e.results[0],a=o.hits,s=o.nbHits,p=on(a,(function(e){return Qt(e)}),c);i.context.searchSuggestions.length0&&(X(),F.current&&F.current.focus())}),[V,X]),yt.useEffect((function(){function e(){if(L.current){var e=.01*window.innerHeight;L.current.style.setProperty("--docsearch-vh","".concat(e,"px"))}}return e(),window.addEventListener("resize",e),function(){window.removeEventListener("resize",e)}}),[]),yt.createElement("div",cn({ref:R},G({"aria-expanded":!0}),{className:["DocSearch","DocSearch-Container","stalled"===T.status&&"DocSearch-Container--Stalled","error"===T.status&&"DocSearch-Container--Errored"].filter(Boolean).join(" "),role:"button",tabIndex:0,onMouseDown:function(e){e.target===e.currentTarget&&u()}}),yt.createElement("div",{className:"DocSearch-Modal",ref:L},yt.createElement("header",{className:"DocSearch-SearchBar",ref:M},yt.createElement(vr,cn({},Q,{state:T,autoFocus:0===V.length,inputRef:F,isFromSelection:Boolean(V)&&V===B,translations:C,onClose:u}))),yt.createElement("div",{className:"DocSearch-Dropdown",ref:H},yt.createElement(lr,cn({},Q,{indexName:n,state:T,hitComponent:p,resultsFooterComponent:d,disableUserPersonalization:j,recentSearches:J,favoriteSearches:$,inputRef:F,translations:N,getMissingResultsUrl:D,onItemClick:function(e,t){W(e),z(e),nn(t)||u()}}))),yt.createElement("footer",{className:"DocSearch-Footer"},yt.createElement(Ot,{translations:x}))))}}}]); \ No newline at end of file diff --git a/assets/js/947f2c39.eed5acb7.js b/assets/js/947f2c39.dd206cf2.js similarity index 97% rename from assets/js/947f2c39.eed5acb7.js rename to assets/js/947f2c39.dd206cf2.js index e2fe83f38c..4f5dfbf265 100644 --- a/assets/js/947f2c39.eed5acb7.js +++ b/assets/js/947f2c39.dd206cf2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7846],{23693:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>y,toc:()=>l});var n=a(58168),p=(a(96540),a(15680));a(67443);const o={id:"custom-types",title:"Custom types",sidebar_label:"Custom types",original_id:"custom-types"},r=void 0,y={unversionedId:"custom-types",id:"version-4.0/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-4.0/custom_types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/4.0/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/custom_types.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types",original_id:"custom-types"},sidebar:"version-4.0/docs",previous:{title:"Pagination",permalink:"/docs/4.0/pagination"},next:{title:"Custom annotations",permalink:"/docs/4.0/field-middlewares"}},i={},l=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],s={toc:l},u="wrapper";function c(e){let{components:t,...a}=e;return(0,p.yg)(u,(0,n.A)({},s,a,{components:t,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field(name="id")\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n')),(0,p.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,p.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,p.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,p.yg)("p",null,"GraphQL comes with an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,p.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')),(0,p.yg)("h2",{id:"usage"},"Usage"),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,p.yg)("p",null,"You can use the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Query")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Field")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,p.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,p.yg)("p",null,"In order to create a custom output type, you need to:"),(0,p.yg)("ol",null,(0,p.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,p.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,p.yg)("p",null,"You'll find more details on the ",(0,p.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,p.yg)("hr",null),(0,p.yg)("p",null,"In order to find existing types, the schema is using ",(0,p.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,p.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,p.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,p.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,p.yg)("p",null,"Any class extending ",(0,p.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,p.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,p.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,p.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,p.yg)("p",null,"The easiest way is to use a ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". This class is used to register custom output types."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper")," instance MUST be registered in your container and linked to a ",(0,p.yg)("inlineCode",{parentName:"p"},"CompositeTypeMapper"),"\nthat will aggregate all the type mappers of the application."),(0,p.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,p.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,p.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,p.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},"create a ",(0,p.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,p.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,p.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,p.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): OutputType;\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,p.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,p.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,p.yg)("p",null,"RootTypeMapper are organized ",(0,p.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,p.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType\n */\n public function mapNameToType(string $typeName): NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return $this->next->mapNameToType($typeName);\n }\n}\n')),(0,p.yg)("p",null,"Now, in order to create an instance of your ",(0,p.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,p.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,p.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,p.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,p.yg)("p",null,"You can register your own root mapper factories using the ",(0,p.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,p.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7846],{23693:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>y,toc:()=>l});var n=a(58168),p=(a(96540),a(15680));a(67443);const r={id:"custom-types",title:"Custom types",sidebar_label:"Custom types",original_id:"custom-types"},o=void 0,y={unversionedId:"custom-types",id:"version-4.0/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-4.0/custom_types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/4.0/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/custom_types.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types",original_id:"custom-types"},sidebar:"version-4.0/docs",previous:{title:"Pagination",permalink:"/docs/4.0/pagination"},next:{title:"Custom annotations",permalink:"/docs/4.0/field-middlewares"}},i={},l=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],s={toc:l},u="wrapper";function c(e){let{components:t,...a}=e;return(0,p.yg)(u,(0,n.A)({},s,a,{components:t,mdxType:"MDXLayout"}),(0,p.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field(name="id")\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n')),(0,p.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,p.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,p.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,p.yg)("p",null,"GraphQL comes with an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,p.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,p.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')),(0,p.yg)("h2",{id:"usage"},"Usage"),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,p.yg)("p",null,"You can use the ",(0,p.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Query")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@Field")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,p.yg)("li",{parentName:"ul"},(0,p.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,p.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,p.yg)("p",null,"In order to create a custom output type, you need to:"),(0,p.yg)("ol",null,(0,p.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,p.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,p.yg)("p",null,"You'll find more details on the ",(0,p.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,p.yg)("hr",null),(0,p.yg)("p",null,"In order to find existing types, the schema is using ",(0,p.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,p.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,p.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,p.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,p.yg)("p",null,"Any class extending ",(0,p.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,p.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,p.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,p.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,p.yg)("p",null,"The easiest way is to use a ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". This class is used to register custom output types."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper")," instance MUST be registered in your container and linked to a ",(0,p.yg)("inlineCode",{parentName:"p"},"CompositeTypeMapper"),"\nthat will aggregate all the type mappers of the application."),(0,p.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,p.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,p.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,p.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,p.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,p.yg)("ul",null,(0,p.yg)("li",{parentName:"ul"},"create a ",(0,p.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,p.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,p.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,p.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,p.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): OutputType;\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,p.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,p.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,p.yg)("p",null,"The ",(0,p.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,p.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,p.yg)("p",null,"RootTypeMapper are organized ",(0,p.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,p.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,p.yg)("p",null,"For instance:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType\n */\n public function mapNameToType(string $typeName): NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return $this->next->mapNameToType($typeName);\n }\n}\n')),(0,p.yg)("p",null,"Now, in order to create an instance of your ",(0,p.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,p.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,p.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,p.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,p.yg)("p",null,"You can register your own root mapper factories using the ",(0,p.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,p.yg)("pre",null,(0,p.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,p.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/950394a4.7223c23c.js b/assets/js/950394a4.647f9c57.js similarity index 99% rename from assets/js/950394a4.7223c23c.js rename to assets/js/950394a4.647f9c57.js index d870d61bc9..bfca9f4d0a 100644 --- a/assets/js/950394a4.7223c23c.js +++ b/assets/js/950394a4.647f9c57.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8194],{19365:(e,n,t)=>{t.d(n,{A:()=>o});var a=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>I});var a=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function h(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[s,u]=g({queryString:t,groupId:a}),[p,m]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),m(e)}),[u,m,i]),tabValues:i}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=u[t].value;a!==l&&(p(n),s(a))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function N(e){const n=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function I(e){const n=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(n)},e))}},28909:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},s=void 0,u={unversionedId:"authentication-authorization",id:"version-7.0.0/authentication-authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some",source:"@site/versioned_docs/version-7.0.0/authentication-authorization.mdx",sourceDirName:".",slug:"/authentication-authorization",permalink:"/docs/authentication-authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/authentication-authorization.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},sidebar:"docs",previous:{title:"User input validation",permalink:"/docs/validation"},next:{title:"Fine grained security",permalink:"/docs/fine-grained-security"}},c={},p=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations / subscriptions",id:"hiding-fields--queries--mutations--subscriptions",level:2}],d={toc:p},h="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(h,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some\nqueries/mutations/subscriptions or fields reserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to\nresources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/subscription/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation/subscription\nthey have no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/implementing-security"},'"authentication service" configured in GraphQLite'),". If user is not authenticated and\nparameter's type is not nullable, an authorization exception is thrown, similar to ",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations--subscriptions"},"Hiding fields / queries / mutations / subscriptions"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/subscriptions/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation. Beware of ",(0,r.yg)("a",{parentName:"p",href:"/docs/annotations-reference"},"it's limitations"),"."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optional from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8194],{19365:(e,n,t)=>{t.d(n,{A:()=>o});var a=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>I});var a=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function h(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[s,u]=g({queryString:t,groupId:a}),[p,m]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),m(e)}),[u,m,i]),tabValues:i}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=u[t].value;a!==l&&(p(n),s(a))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function N(e){const n=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function I(e){const n=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(n)},e))}},28909:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},s=void 0,u={unversionedId:"authentication-authorization",id:"version-7.0.0/authentication-authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some",source:"@site/versioned_docs/version-7.0.0/authentication-authorization.mdx",sourceDirName:".",slug:"/authentication-authorization",permalink:"/docs/authentication-authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/authentication-authorization.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},sidebar:"docs",previous:{title:"User input validation",permalink:"/docs/validation"},next:{title:"Fine grained security",permalink:"/docs/fine-grained-security"}},c={},p=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations / subscriptions",id:"hiding-fields--queries--mutations--subscriptions",level:2}],d={toc:p},h="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(h,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some\nqueries/mutations/subscriptions or fields reserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to\nresources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/subscription/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation/subscription\nthey have no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/implementing-security"},'"authentication service" configured in GraphQLite'),". If user is not authenticated and\nparameter's type is not nullable, an authorization exception is thrown, similar to ",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations--subscriptions"},"Hiding fields / queries / mutations / subscriptions"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/subscriptions/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation. Beware of ",(0,r.yg)("a",{parentName:"p",href:"/docs/annotations-reference"},"it's limitations"),"."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optional from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/95576100.d7e099eb.js b/assets/js/95576100.7cd86243.js similarity index 73% rename from assets/js/95576100.d7e099eb.js rename to assets/js/95576100.7cd86243.js index 0246bbfa7f..51c392750a 100644 --- a/assets/js/95576100.d7e099eb.js +++ b/assets/js/95576100.7cd86243.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1898],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>T});var a=n(58168),r=n(96540),i=n(20053),o=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function p(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:n,groupId:a}),[d,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(d(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),n??t)})))}function b(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,a.A)({},e,t)),r.createElement(b,(0,a.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},52236:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var a=n(58168),r=(n(96540),n(15680)),i=(n(67443),n(11470)),o=n(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-4.3/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-4.3/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/4.3/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/autowiring.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"version-4.3/docs",previous:{title:"Type mapping",permalink:"/docs/4.3/type-mapping"},next:{title:"Extending a type",permalink:"/docs/4.3/extend-type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1898],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),i=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(n.location.search);t.set(i,e),n.replace({...n.location,search:t.toString()})}),[i,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,i]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:n}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,t)),r.createElement(b,(0,n.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},52236:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),i=(a(67443),a(11470)),o=a(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-4.3/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-4.3/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/4.3/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/autowiring.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"version-4.3/docs",previous:{title:"Type mapping",permalink:"/docs/4.3/type-mapping"},next:{title:"Extending a type",permalink:"/docs/4.3/extend-type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(h,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9664ee55.af355be4.js b/assets/js/9664ee55.54f363d2.js similarity index 99% rename from assets/js/9664ee55.af355be4.js rename to assets/js/9664ee55.54f363d2.js index b93d30236e..4f174b4c26 100644 --- a/assets/js/9664ee55.af355be4.js +++ b/assets/js/9664ee55.54f363d2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7544],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var r=a(96540),t=a(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>$});var r=a(58168),t=a(96540),o=a(20053),i=a(23104),l=a(56347),s=a(57485),c=a(31682),p=a(89466);function u(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:r,default:t}}=e;return{value:n,label:a,attributes:r,default:t}}))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function h(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:a}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,c]=m({queryString:a,groupId:r}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,p.Dv)(a);return[r,(0,t.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:r}),g=(()=>{const e=s??u;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),c(e),y(e)}),[c,y,o]),tabValues:o}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:s,tabValues:c}=e;const p=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),d=e=>{const n=e.currentTarget,a=p.indexOf(n),r=c[a].value;r!==l&&(u(n),s(r))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;n=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;n=p[a]??p[p.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return t.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>p.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),a??n)})))}function w(e){let{lazy:n,children:a,selectedValue:r}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,r.A)({},e,n)),t.createElement(w,(0,r.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,r.A)({key:String(n)},e))}},14396:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>c,toc:()=>u});var r=a(58168),t=(a(96540),a(15680)),o=(a(67443),a(11470)),i=a(19365);const l={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},s=void 0,c={unversionedId:"other-frameworks",id:"version-4.2/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-4.2/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/4.2/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/other-frameworks.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"version-4.2/docs",previous:{title:"Universal service providers",permalink:"/docs/4.2/universal-service-providers"},next:{title:"Queries",permalink:"/docs/4.2/queries"}},p={},u=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],d={toc:u},h="wrapper";function m(e){let{components:n,...a}=e;return(0,t.yg)(h,(0,r.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'$builder->setUrl("/graphql"); // Modify the URL endpoint (defaults to /graphql)\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object. Use this object to configure Webonyx in details.\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n')),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,t.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n"))),(0,t.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")))),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7544],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var r=a(96540),t=a(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>$});var r=a(58168),t=a(96540),o=a(20053),i=a(23104),l=a(56347),s=a(57485),c=a(31682),p=a(89466);function u(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:r,default:t}}=e;return{value:n,label:a,attributes:r,default:t}}))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function h(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:a}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,c]=m({queryString:a,groupId:r}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,p.Dv)(a);return[r,(0,t.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:r}),g=(()=>{const e=s??u;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),c(e),y(e)}),[c,y,o]),tabValues:o}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:s,tabValues:c}=e;const p=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),d=e=>{const n=e.currentTarget,a=p.indexOf(n),r=c[a].value;r!==l&&(u(n),s(r))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;n=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;n=p[a]??p[p.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return t.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>p.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),a??n)})))}function w(e){let{lazy:n,children:a,selectedValue:r}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,r.A)({},e,n)),t.createElement(w,(0,r.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,r.A)({key:String(n)},e))}},14396:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>c,toc:()=>u});var r=a(58168),t=(a(96540),a(15680)),o=(a(67443),a(11470)),i=a(19365);const l={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},s=void 0,c={unversionedId:"other-frameworks",id:"version-4.2/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-4.2/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/4.2/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/other-frameworks.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"version-4.2/docs",previous:{title:"Universal service providers",permalink:"/docs/4.2/universal-service-providers"},next:{title:"Queries",permalink:"/docs/4.2/queries"}},p={},u=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],d={toc:u},h="wrapper";function m(e){let{components:n,...a}=e;return(0,t.yg)(h,(0,r.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'$builder->setUrl("/graphql"); // Modify the URL endpoint (defaults to /graphql)\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object. Use this object to configure Webonyx in details.\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n')),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,t.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n"))),(0,t.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")))),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/96877411.11d1c90c.js b/assets/js/96877411.eb9ab217.js similarity index 98% rename from assets/js/96877411.11d1c90c.js rename to assets/js/96877411.eb9ab217.js index 13d735a727..7b5ed2dae7 100644 --- a/assets/js/96877411.11d1c90c.js +++ b/assets/js/96877411.eb9ab217.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5526],{80863:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},l=void 0,o={unversionedId:"extend-type",id:"extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/docs/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/next/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/extend-type.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"docs",previous:{title:"Autowiring services",permalink:"/docs/next/autowiring"},next:{title:"External type declaration",permalink:"/docs/next/external-type-declaration"}},s={},p=[],d={toc:p},c="wrapper";function u(e){let{components:t,...n}=e;return(0,i.yg)(c,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," attribute to add additional fields to a type that is already declared."),(0,i.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,i.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,i.yg)("p",null,"Let's assume you have a ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,i.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,i.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,i.yg)("p",null,"Using ",(0,i.yg)("inlineCode",{parentName:"p"},"#[ExtendType]"),", you can add an additional ",(0,i.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")),(0,i.yg)("p",null,"Let's break this sample:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n")),(0,i.yg)("p",null,"With the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," attribute, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,i.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,i.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Field]"),' attribute is used to add the "name" field to the ',(0,i.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,i.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,i.yg)("p",null,'Using the "',(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,i.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5526],{80863:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>u,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},l=void 0,o={unversionedId:"extend-type",id:"extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/docs/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/next/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/extend-type.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"docs",previous:{title:"Autowiring services",permalink:"/docs/next/autowiring"},next:{title:"External type declaration",permalink:"/docs/next/external-type-declaration"}},s={},p=[],d={toc:p},c="wrapper";function u(e){let{components:t,...n}=e;return(0,i.yg)(c,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," attribute to add additional fields to a type that is already declared."),(0,i.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,i.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,i.yg)("p",null,"Let's assume you have a ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,i.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,i.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,i.yg)("p",null,"Using ",(0,i.yg)("inlineCode",{parentName:"p"},"#[ExtendType]"),", you can add an additional ",(0,i.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")),(0,i.yg)("p",null,"Let's break this sample:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n")),(0,i.yg)("p",null,"With the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," attribute, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,i.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,i.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Field]"),' attribute is used to add the "name" field to the ',(0,i.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,i.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,i.yg)("p",null,'Using the "',(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,i.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9749ab4a.5a374ae4.js b/assets/js/9749ab4a.8051e985.js similarity index 98% rename from assets/js/9749ab4a.5a374ae4.js rename to assets/js/9749ab4a.8051e985.js index d3c5de1384..3f231ccafb 100644 --- a/assets/js/9749ab4a.5a374ae4.js +++ b/assets/js/9749ab4a.8051e985.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7110],{13401:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>d,contentTitle:()=>r,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var t=n(58168),i=(n(96540),n(15680));n(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"version-5.0/changelog",title:"Changelog",description:"5.0.0",source:"@site/versioned_docs/version-5.0/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/5.0/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/CHANGELOG.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"version-5.0/docs",previous:{title:"Semantic versioning",permalink:"/docs/5.0/semver"}},d={},p=[{value:"5.0.0",id:"500",level:2},{value:"Dependencies:",id:"dependencies",level:4},{value:"4.3.0",id:"430",level:2},{value:"Breaking change:",id:"breaking-change",level:4},{value:"Minor changes:",id:"minor-changes",level:4},{value:"4.2.0",id:"420",level:2},{value:"Breaking change:",id:"breaking-change-1",level:4},{value:"New features:",id:"new-features",level:4},{value:"4.1.0",id:"410",level:2},{value:"Breaking change:",id:"breaking-change-2",level:4},{value:"New features:",id:"new-features-1",level:4},{value:"Minor changes:",id:"minor-changes-1",level:4},{value:"Miscellaneous:",id:"miscellaneous",level:4},{value:"4.0.0",id:"400",level:2},{value:"New features:",id:"new-features-2",level:4},{value:"Symfony:",id:"symfony",level:4},{value:"Laravel:",id:"laravel",level:4},{value:"Internals:",id:"internals",level:4}],s={toc:p},g="wrapper";function u(e){let{components:a,...n}=e;return(0,i.yg)(g,(0,t.A)({},s,n,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"500"},"5.0.0"),(0,i.yg)("h4",{id:"dependencies"},"Dependencies:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Upgraded to using version 14.9 of ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/webonyx/graphql-php"},"webonyx/graphql-php"))),(0,i.yg)("h2",{id:"430"},"4.3.0"),(0,i.yg)("h4",{id:"breaking-change"},"Breaking change:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The method ",(0,i.yg)("inlineCode",{parentName:"li"},"setAnnotationCacheDir($directory)")," has been removed from the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),". The annotation\ncache will use your ",(0,i.yg)("inlineCode",{parentName:"li"},"Psr\\SimpleCache\\CacheInterface")," compliant cache handler set through the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),"\nconstructor.")),(0,i.yg)("h4",{id:"minor-changes"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Removed dependency for doctrine/cache and unified some of the cache layers following a PSR interface."),(0,i.yg)("li",{parentName:"ul"},"Cleaned up some of the documentation in an attempt to get things accurate with versioned releases.")),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h4",{id:"breaking-change-1"},"Breaking change:"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h4",{id:"new-features"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"@Type")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/input-types#input-annotation"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Logged"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h4",{id:"breaking-change-2"},"Breaking change:"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h4",{id:"new-features-1"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h4",{id:"minor-changes-1"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h4",{id:"miscellaneous"},"Miscellaneous:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h4",{id:"new-features-2"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h4",{id:"symfony"},"Symfony:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h4",{id:"laravel"},"Laravel:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h4",{id:"internals"},"Internals:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7110],{13401:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>d,contentTitle:()=>r,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var t=n(58168),i=(n(96540),n(15680));n(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"version-5.0/changelog",title:"Changelog",description:"5.0.0",source:"@site/versioned_docs/version-5.0/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/5.0/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/CHANGELOG.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"version-5.0/docs",previous:{title:"Semantic versioning",permalink:"/docs/5.0/semver"}},d={},p=[{value:"5.0.0",id:"500",level:2},{value:"Dependencies:",id:"dependencies",level:4},{value:"4.3.0",id:"430",level:2},{value:"Breaking change:",id:"breaking-change",level:4},{value:"Minor changes:",id:"minor-changes",level:4},{value:"4.2.0",id:"420",level:2},{value:"Breaking change:",id:"breaking-change-1",level:4},{value:"New features:",id:"new-features",level:4},{value:"4.1.0",id:"410",level:2},{value:"Breaking change:",id:"breaking-change-2",level:4},{value:"New features:",id:"new-features-1",level:4},{value:"Minor changes:",id:"minor-changes-1",level:4},{value:"Miscellaneous:",id:"miscellaneous",level:4},{value:"4.0.0",id:"400",level:2},{value:"New features:",id:"new-features-2",level:4},{value:"Symfony:",id:"symfony",level:4},{value:"Laravel:",id:"laravel",level:4},{value:"Internals:",id:"internals",level:4}],s={toc:p},g="wrapper";function u(e){let{components:a,...n}=e;return(0,i.yg)(g,(0,t.A)({},s,n,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"500"},"5.0.0"),(0,i.yg)("h4",{id:"dependencies"},"Dependencies:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Upgraded to using version 14.9 of ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/webonyx/graphql-php"},"webonyx/graphql-php"))),(0,i.yg)("h2",{id:"430"},"4.3.0"),(0,i.yg)("h4",{id:"breaking-change"},"Breaking change:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The method ",(0,i.yg)("inlineCode",{parentName:"li"},"setAnnotationCacheDir($directory)")," has been removed from the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),". The annotation\ncache will use your ",(0,i.yg)("inlineCode",{parentName:"li"},"Psr\\SimpleCache\\CacheInterface")," compliant cache handler set through the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),"\nconstructor.")),(0,i.yg)("h4",{id:"minor-changes"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Removed dependency for doctrine/cache and unified some of the cache layers following a PSR interface."),(0,i.yg)("li",{parentName:"ul"},"Cleaned up some of the documentation in an attempt to get things accurate with versioned releases.")),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h4",{id:"breaking-change-1"},"Breaking change:"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h4",{id:"new-features"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"@Type")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/input-types#input-annotation"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Logged"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h4",{id:"breaking-change-2"},"Breaking change:"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h4",{id:"new-features-1"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h4",{id:"minor-changes-1"},"Minor changes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h4",{id:"miscellaneous"},"Miscellaneous:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h4",{id:"new-features-2"},"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h4",{id:"symfony"},"Symfony:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h4",{id:"laravel"},"Laravel:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/5.0/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h4",{id:"internals"},"Internals:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/976f6afc.1e2e0c2e.js b/assets/js/976f6afc.00b1895b.js similarity index 98% rename from assets/js/976f6afc.1e2e0c2e.js rename to assets/js/976f6afc.00b1895b.js index 3062620f08..10d41fdf5e 100644 --- a/assets/js/976f6afc.1e2e0c2e.js +++ b/assets/js/976f6afc.00b1895b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9717],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const o={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),o=a(20053),u=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=p(e),[u,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,o]),tabValues:o}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,o.A)("tabs__item",v.tabItem,u?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function T(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},13769:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),o=(a(67443),a(11470)),u=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},s=void 0,i={unversionedId:"mutations",id:"version-4.1/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-4.1/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/4.1/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/mutations.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},sidebar:"version-4.1/docs",previous:{title:"Queries",permalink:"/docs/4.1/queries"},next:{title:"Type mapping",permalink:"/docs/4.1/type_mapping"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9717],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const o={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),o=a(20053),u=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=p(e),[u,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,o]),tabValues:o}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,o.A)("tabs__item",v.tabItem,u?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function T(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},13769:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),o=(a(67443),a(11470)),u=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},s=void 0,i={unversionedId:"mutations",id:"version-4.1/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-4.1/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/4.1/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/mutations.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations",original_id:"mutations"},sidebar:"version-4.1/docs",previous:{title:"Queries",permalink:"/docs/4.1/queries"},next:{title:"Type mapping",permalink:"/docs/4.1/type_mapping"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9953ecde.c796fb6a.js b/assets/js/9953ecde.dc248cb7.js similarity index 97% rename from assets/js/9953ecde.c796fb6a.js rename to assets/js/9953ecde.dc248cb7.js index 66fde97712..5e82d73780 100644 --- a/assets/js/9953ecde.c796fb6a.js +++ b/assets/js/9953ecde.dc248cb7.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3308],{32608:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/docs/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/next/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/semver.md",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"docs",previous:{title:"Attributes reference",permalink:"/docs/next/annotations-reference"},next:{title:"Changelog",permalink:"/docs/next/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3308],{32608:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},o=void 0,s={unversionedId:"semver",id:"semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as Semantic Versioning. In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example).",source:"@site/docs/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/next/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/semver.md",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning"},sidebar:"docs",previous:{title:"Attributes reference",permalink:"/docs/next/annotations-reference"},next:{title:"Changelog",permalink:"/docs/next/changelog"}},l={},p=[],m={toc:p},u="wrapper";function c(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all minor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short, Semantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility. Minor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of that release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental")," and their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a major version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},(0,n.yg)("p",{parentName:"li"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"p"},"composer.json"),", target a minor version:"),(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library. They are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9bd507da.f6812058.js b/assets/js/9bd507da.92df8552.js similarity index 89% rename from assets/js/9bd507da.f6812058.js rename to assets/js/9bd507da.92df8552.js index c0d3d30920..003f85377e 100644 --- a/assets/js/9bd507da.f6812058.js +++ b/assets/js/9bd507da.92df8552.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1565],{65890:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>u});var i=n(58168),r=(n(96540),n(15680));n(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-4.2/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories.",source:"@site/versioned_docs/version-4.2/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/4.2/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/implementing-security.md",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},sidebar:"version-4.2/docs",previous:{title:"Fine grained security",permalink:"/docs/4.2/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/4.2/query-plan"}},c={},u=[],l={toc:u},p="wrapper";function h(e){let{components:t,...n}=e;return(0,r.yg)(p,(0,i.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories."),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1565],{65890:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),r=(i(96540),i(15680));i(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-4.2/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories.",source:"@site/versioned_docs/version-4.2/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/4.2/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/implementing-security.md",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},sidebar:"version-4.2/docs",previous:{title:"Fine grained security",permalink:"/docs/4.2/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/4.2/query-plan"}},c={},l=[],u={toc:l},p="wrapper";function h(e){let{components:t,...i}=e;return(0,r.yg)(p,(0,n.A)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories."),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9d336ee4.d1d3fbf7.js b/assets/js/9d336ee4.ee532efb.js similarity index 99% rename from assets/js/9d336ee4.d1d3fbf7.js rename to assets/js/9d336ee4.ee532efb.js index 59be5eb8ec..39cdf416f0 100644 --- a/assets/js/9d336ee4.d1d3fbf7.js +++ b/assets/js/9d336ee4.ee532efb.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9778],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),i=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},50054:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const i={id:"queries",title:"Queries",sidebar_label:"Queries"},s=void 0,u={unversionedId:"queries",id:"version-6.0/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-6.0/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/6.0/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/queries.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/6.0/other-frameworks"},next:{title:"Mutations",permalink:"/docs/6.0/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...i}=e;return(0,r.yg)(h,(0,a.A)({},d,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9778],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),i=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},50054:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const i={id:"queries",title:"Queries",sidebar_label:"Queries"},s=void 0,u={unversionedId:"queries",id:"version-6.0/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-6.0/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/6.0/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/queries.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/6.0/other-frameworks"},next:{title:"Mutations",permalink:"/docs/6.0/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...i}=e;return(0,r.yg)(h,(0,a.A)({},d,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file diff --git a/assets/js/9d9f8394.57f61e6c.js b/assets/js/9d9f8394.fab55070.js similarity index 88% rename from assets/js/9d9f8394.57f61e6c.js rename to assets/js/9d9f8394.fab55070.js index 5f2872d5b0..433bb0cbbd 100644 --- a/assets/js/9d9f8394.57f61e6c.js +++ b/assets/js/9d9f8394.fab55070.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9013],{9036:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>a,default:()=>g,frontMatter:()=>i,metadata:()=>s,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},a=void 0,s={unversionedId:"troubleshooting",id:"troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/docs/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/next/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/troubleshooting.md",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"docs",previous:{title:"Internals",permalink:"/docs/next/internals"},next:{title:"Migrating",permalink:"/docs/next/migrating"}},l={},u=[],p={toc:u},c="wrapper";function g(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,n.A)({},p,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9013],{9036:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>a,default:()=>g,frontMatter:()=>i,metadata:()=>s,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},a=void 0,s={unversionedId:"troubleshooting",id:"troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/docs/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/next/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/troubleshooting.md",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"docs",previous:{title:"Internals",permalink:"/docs/next/internals"},next:{title:"Migrating",permalink:"/docs/next/migrating"}},l={},u=[],c={toc:u},p="wrapper";function g(e){let{components:t,...o}=e;return(0,r.yg)(p,(0,n.A)({},c,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9f0ecd2e.246fb3f2.js b/assets/js/9f0ecd2e.ae5e64e1.js similarity index 98% rename from assets/js/9f0ecd2e.246fb3f2.js rename to assets/js/9f0ecd2e.ae5e64e1.js index ecc5c7cc6c..9abd4eaefc 100644 --- a/assets/js/9f0ecd2e.246fb3f2.js +++ b/assets/js/9f0ecd2e.ae5e64e1.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4893],{70261:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var i=t(58168),a=(t(96540),t(15680));t(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},r=void 0,o={unversionedId:"field-middlewares",id:"version-6.1/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.1/field-middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/6.1/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/field-middlewares.md",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},sidebar:"docs",previous:{title:"Custom types",permalink:"/docs/6.1/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/6.1/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour of a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:t(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library by PHP 8 attributes."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="OnlyDebug.php"',title:'"OnlyDebug.php"'},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation is to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:"),(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"p"},"graphql.field_middleware")," tag."))))}c.isMDXComponent=!0},8643:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4893],{70261:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var i=t(58168),a=(t(96540),t(15680));t(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},r=void 0,o={unversionedId:"field-middlewares",id:"version-6.1/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.1/field-middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/6.1/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/field-middlewares.md",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},sidebar:"docs",previous:{title:"Custom types",permalink:"/docs/6.1/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/6.1/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour of a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:t(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library by PHP 8 attributes."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="OnlyDebug.php"',title:'"OnlyDebug.php"'},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation is to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:"),(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"p"},"graphql.field_middleware")," tag."))))}c.isMDXComponent=!0},8643:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file diff --git a/assets/js/a0bf4a5f.182ebb00.js b/assets/js/a0bf4a5f.74861ab2.js similarity index 98% rename from assets/js/a0bf4a5f.182ebb00.js rename to assets/js/a0bf4a5f.74861ab2.js index b9900a0895..a88567832c 100644 --- a/assets/js/a0bf4a5f.182ebb00.js +++ b/assets/js/a0bf4a5f.74861ab2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4876],{49317:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var i=t(58168),a=(t(96540),t(15680));t(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},r=void 0,o={unversionedId:"field-middlewares",id:"version-4.3/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.3/field-middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/4.3/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/field-middlewares.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},sidebar:"version-4.3/docs",previous:{title:"Custom types",permalink:"/docs/4.3/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/4.3/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour of a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:t(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library (for PHP 7+) and/or by PHP 8 attributes."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="OnlyDebug.php"',title:'"OnlyDebug.php"'},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation is to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:"),(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"p"},"graphql.field_middleware")," tag."))))}c.isMDXComponent=!0},8643:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4876],{49317:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var i=t(58168),a=(t(96540),t(15680));t(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},r=void 0,o={unversionedId:"field-middlewares",id:"version-4.3/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.3/field-middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/4.3/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/field-middlewares.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},sidebar:"version-4.3/docs",previous:{title:"Custom types",permalink:"/docs/4.3/custom-types"},next:{title:"Custom argument resolving",permalink:"/docs/4.3/argument-resolving"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour of a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:t(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library (for PHP 7+) and/or by PHP 8 attributes."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="OnlyDebug.php"',title:'"OnlyDebug.php"'},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation is to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:"),(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("p",{parentName:"li"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"p"},"graphql.field_middleware")," tag."))))}c.isMDXComponent=!0},8643:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file diff --git a/assets/js/a13f3cdc.e5f640b2.js b/assets/js/a13f3cdc.672b78da.js similarity index 98% rename from assets/js/a13f3cdc.e5f640b2.js rename to assets/js/a13f3cdc.672b78da.js index 1e61d8578f..09a9e9b9d3 100644 --- a/assets/js/a13f3cdc.e5f640b2.js +++ b/assets/js/a13f3cdc.672b78da.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[835],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),l=n(23104),o=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),b=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},l,{className:(0,s.A)("tabs__item",y.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",y.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,b.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},34394:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>o,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),l=n(19365);const o={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,u={unversionedId:"prefetch-method",id:"version-4.3/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-4.3/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/4.3/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/prefetch-method.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"version-4.3/docs",previous:{title:"Query plan",permalink:"/docs/4.3/query-plan"},next:{title:"File uploads",permalink:"/docs/4.3/file-uploads"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))),(0,r.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[835],{19365:(e,t,n)=>{n.d(t,{A:()=>l});var a=n(96540),r=n(20053);const s={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(s.tabItem,l),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),s=n(20053),l=n(23104),o=n(56347),i=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),s=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,i.aZ)(s),(0,r.useCallback)((e=>{if(!s)return;const t=new URLSearchParams(a.location.search);t.set(s,e),a.replace({...a.location,search:t.toString()})}),[s,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,s=h(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:s}))),[i,u]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,s]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&s.set(e)}),[n,s])]}({groupId:a}),b=(()=>{const e=i??p;return d({value:e,tabValues:s})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:s}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),f(e)}),[u,f,s]),tabValues:s}}var b=n(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:o,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.a_)(),h=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==o&&(p(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:h},l,{className:(0,s.A)("tabs__item",y.tabItem,l?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=f(e);return r.createElement("div",{className:(0,s.A)("tabs-container",y.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,b.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},34394:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>o,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),s=(n(67443),n(11470)),l=n(19365);const o={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,u={unversionedId:"prefetch-method",id:"version-4.3/prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/versioned_docs/version-4.3/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/4.3/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/prefetch-method.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"version-4.3/docs",previous:{title:"Query plan",permalink:"/docs/4.3/query-plan"},next:{title:"File uploads",permalink:"/docs/4.3/file-uploads"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],h={toc:p},d="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(d,(0,a.A)({},h,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field(prefetchMethod: "prefetchUsers")]\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchUsers")\n * @param mixed $prefetchedUsers\n * @return User\n */\n public function getUser($prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')))),(0,r.yg)("p",null,'When the "prefetchMethod" attribute is detected in the "@Field" annotation, the method is called automatically.\nThe first argument of the method is an array of instances of the main type.\nThe "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@Field" annotated method.'),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the @Field annotated method OR/AND on the prefetchMethod."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(s.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field(prefetchMethod: "prefetchComments")]\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type\n */\nclass PostType {\n /**\n * @Field(prefetchMethod="prefetchComments")\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n public function getComments($prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))),(0,r.yg)("p",null,"The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a16ee953.1dff7035.js b/assets/js/a16ee953.9a68612f.js similarity index 96% rename from assets/js/a16ee953.1dff7035.js rename to assets/js/a16ee953.9a68612f.js index 535cd32204..8979eec9eb 100644 --- a/assets/js/a16ee953.1dff7035.js +++ b/assets/js/a16ee953.9a68612f.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4224],{27244:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,o={unversionedId:"internals",id:"version-6.0/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-6.0/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/6.0/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/internals.md",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"docs",previous:{title:"Laravel specific features",permalink:"/docs/6.0/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/6.0/troubleshooting"}},l={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types (Deprecated: use native enums)"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"EnumTypeMapper"),": maps PHP enums to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/6.0/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4224],{27244:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>o,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,l={unversionedId:"internals",id:"version-6.0/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-6.0/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/6.0/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/internals.md",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"docs",previous:{title:"Laravel specific features",permalink:"/docs/6.0/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/6.0/troubleshooting"}},o={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types (Deprecated: use native enums)"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"EnumTypeMapper"),": maps PHP enums to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eEnumTypeMapper\n EnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/6.0/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a1e3d512.f4a5eedb.js b/assets/js/a1e3d512.9293ad8e.js similarity index 99% rename from assets/js/a1e3d512.f4a5eedb.js rename to assets/js/a1e3d512.9293ad8e.js index 2f8ce7e64a..324d318fea 100644 --- a/assets/js/a1e3d512.f4a5eedb.js +++ b/assets/js/a1e3d512.9293ad8e.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9949],{19365:(e,a,t)=>{t.d(a,{A:()=>l});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function l(e){let{children:a,hidden:t,className:l}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,l),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),i=t(20053),l=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,o.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=g(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),m=(()=>{const e=s??c;return d({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),h(e)}),[u,h,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,l.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==o&&(c(a),s(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:l}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},l,{className:(0,i.A)("tabs__item",f.tabItem,l?.className,{"tabs__item--active":o===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},26337:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),l=t(19365);const o={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security",original_id:"fine-grained-security"},s=void 0,u={unversionedId:"fine-grained-security",id:"version-4.1/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-4.1/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/4.1/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/fine-grained-security.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security",original_id:"fine-grained-security"},sidebar:"version-4.1/docs",previous:{title:"Authentication and authorization",permalink:"/docs/4.1/authentication_authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/4.1/implementing-security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/authentication_authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"PHP 7")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/authentication_authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9949],{19365:(e,a,t)=>{t.d(a,{A:()=>l});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function l(e){let{children:a,hidden:t,className:l}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,l),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),i=t(20053),l=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,o.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=g(e),[l,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),m=(()=>{const e=s??c;return d({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),h(e)}),[u,h,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,l.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==o&&(c(a),s(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:l}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},l,{className:(0,i.A)("tabs__item",f.tabItem,l?.className,{"tabs__item--active":o===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},26337:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),l=t(19365);const o={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security",original_id:"fine-grained-security"},s=void 0,u={unversionedId:"fine-grained-security",id:"version-4.1/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-4.1/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/4.1/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/fine-grained-security.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security",original_id:"fine-grained-security"},sidebar:"version-4.1/docs",previous:{title:"Authentication and authorization",permalink:"/docs/4.1/authentication_authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/4.1/implementing-security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/authentication_authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"PHP 7")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/authentication_authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a23a5b68.c6e07c87.js b/assets/js/a23a5b68.b5d885a3.js similarity index 94% rename from assets/js/a23a5b68.c6e07c87.js rename to assets/js/a23a5b68.b5d885a3.js index bdc3e76459..d6c07a82bf 100644 --- a/assets/js/a23a5b68.c6e07c87.js +++ b/assets/js/a23a5b68.b5d885a3.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8093],{19365:(e,a,n)=>{n.d(a,{A:()=>i});var t=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:n,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},a)}},11470:(e,a,n)=>{n.d(a,{A:()=>P});var t=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:n,attributes:t,default:r}}=e;return{value:a,label:n,attributes:t,default:r}}))}function c(e){const{values:a,children:n}=e;return(0,r.useMemo)((()=>{const e=a??d(n);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,n])}function g(e){let{value:a,tabValues:n}=e;return n.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:n}=e;const t=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:n}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:a,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(t.location.search);a.set(l,e),t.replace({...t.location,search:a.toString()})}),[l,t])]}function m(e){const{defaultValue:a,queryString:n=!1,groupId:t}=e,l=c(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const t=n.find((e=>e.default))??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:n,groupId:t}),[d,m]=function(e){let{groupId:a}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(a),[t,l]=(0,p.Dv)(n);return[t,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:t}),y=(()=>{const e=s??d;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:d}=(0,i.a_)(),c=e=>{const a=e.currentTarget,n=p.indexOf(a),t=u[n].value;t!==o&&(d(a),s(t))},g=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;a=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;a=p[n]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},a)},u.map((e=>{let{value:a,label:n,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:c},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),n??a)})))}function b(e){let{lazy:a,children:n,selectedValue:t}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==t}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,t.A)({},e,a)),r.createElement(b,(0,t.A)({},e,a)))}function P(e){const a=(0,y.A)();return r.createElement(w,(0,t.A)({key:String(a)},e))}},24483:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>d});var t=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-6.0/laravel-package-advanced",title:"Laravel package: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.0/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/6.0/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/laravel-package-advanced.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},sidebar:"docs",previous:{title:"Symfony specific features",permalink:"/docs/6.0/symfony-bundle-advanced"},next:{title:"Internals",permalink:"/docs/6.0/internals"}},p={},d=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3}],c={toc:d},g="wrapper";function h(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},c,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n'))))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8093],{19365:(e,a,n)=>{n.d(a,{A:()=>i});var t=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:n,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},a)}},11470:(e,a,n)=>{n.d(a,{A:()=>P});var t=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:n,attributes:t,default:r}}=e;return{value:a,label:n,attributes:t,default:r}}))}function d(e){const{values:a,children:n}=e;return(0,r.useMemo)((()=>{const e=a??c(n);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,n])}function g(e){let{value:a,tabValues:n}=e;return n.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:n}=e;const t=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:n}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:a,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(t.location.search);a.set(l,e),t.replace({...t.location,search:a.toString()})}),[l,t])]}function m(e){const{defaultValue:a,queryString:n=!1,groupId:t}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const t=n.find((e=>e.default))??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:n,groupId:t}),[c,m]=function(e){let{groupId:a}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(a),[t,l]=(0,p.Dv)(n);return[t,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:t}),y=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const a=e.currentTarget,n=p.indexOf(a),t=u[n].value;t!==o&&(c(a),s(t))},g=e=>{let a=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;a=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;a=p[n]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},a)},u.map((e=>{let{value:a,label:n,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),n??a)})))}function b(e){let{lazy:a,children:n,selectedValue:t}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==t}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,t.A)({},e,a)),r.createElement(b,(0,t.A)({},e,a)))}function P(e){const a=(0,y.A)();return r.createElement(w,(0,t.A)({key:String(a)},e))}},24483:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var t=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-6.0/laravel-package-advanced",title:"Laravel package: advanced usage",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.0/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/6.0/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/laravel-package-advanced.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features"},sidebar:"docs",previous:{title:"Symfony specific features",permalink:"/docs/6.0/symfony-bundle-advanced"},next:{title:"Internals",permalink:"/docs/6.0/internals"}},p={},c=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3}],d={toc:c},g="wrapper";function h(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n'))))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a264d631.d36b1f3c.js b/assets/js/a264d631.4035d270.js similarity index 99% rename from assets/js/a264d631.d36b1f3c.js rename to assets/js/a264d631.4035d270.js index 3b7ed8ff65..4162176e83 100644 --- a/assets/js/a264d631.d36b1f3c.js +++ b/assets/js/a264d631.4035d270.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7542],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},52541:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-5.0/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-5.0/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/5.0/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/error-handling.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"version-5.0/docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/5.0/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/5.0/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/5.0/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/5.0/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7542],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},52541:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-5.0/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-5.0/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/5.0/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/error-handling.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"version-5.0/docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/5.0/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/5.0/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/5.0/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/5.0/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a27ea030.7525b406.js b/assets/js/a27ea030.3bd08244.js similarity index 92% rename from assets/js/a27ea030.7525b406.js rename to assets/js/a27ea030.3bd08244.js index a160a64a9a..a1c3690008 100644 --- a/assets/js/a27ea030.7525b406.js +++ b/assets/js/a27ea030.3bd08244.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[68],{95007:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>h,frontMatter:()=>n,metadata:()=>u,toc:()=>l});var i=r(58168),s=(r(96540),r(15680)),a=r(67443);const n={id:"automatic-persisted-queries",title:"Automatic persisted queries",sidebar_label:"Automatic persisted queries"},o=void 0,u={unversionedId:"automatic-persisted-queries",id:"version-7.0.0/automatic-persisted-queries",title:"Automatic persisted queries",description:"The problem",source:"@site/versioned_docs/version-7.0.0/automatic-persisted-queries.mdx",sourceDirName:".",slug:"/automatic-persisted-queries",permalink:"/docs/automatic-persisted-queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/automatic-persisted-queries.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"automatic-persisted-queries",title:"Automatic persisted queries",sidebar_label:"Automatic persisted queries"},sidebar:"docs",previous:{title:"Prefetching records",permalink:"/docs/prefetch-method"},next:{title:"File uploads",permalink:"/docs/file-uploads"}},d={},l=[{value:"The problem",id:"the-problem",level:2},{value:"Apollo APQ",id:"apollo-apq",level:2},{value:"Setup",id:"setup",level:2}],p={toc:l},c="wrapper";function h(e){let{components:t,...r}=e;return(0,s.yg)(c,(0,i.A)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,s.yg)("h2",{id:"the-problem"},"The problem"),(0,s.yg)("p",null,"Clients send queries to GraphQLite as HTTP requests that include the GraphQL string of the query to execute.\nDepending on your graph's schema, the size of a valid query string might be arbitrarily large.\nAs query strings become larger, increased latency and network usage can noticeably degrade client performance."),(0,s.yg)("p",null,'To combat this, GraphQL servers use a technique called "persisted queries". The basic idea is instead of\nsending the whole query string, clients only send it\'s unique identifier. The server then finds the actual\nquery string by given identifier and use that as if the client sent the whole query in the first place.\nThat helps improve GraphQL network performance with zero build-time configuration by sending smaller GraphQL HTTP requests.\nA smaller request payload reduces bandwidth utilization and speeds up GraphQL Client loading times.'),(0,s.yg)("h2",{id:"apollo-apq"},"Apollo APQ"),(0,s.yg)("p",null,(0,s.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/apollo-server/performance/apq/"},"Automatic persisted queries (APQ) is technique created by Apollo"),"\nand is aimed to implement a simple automatic way of persisting queries. Queries are cached on the server side,\nalong with its unique identifier (always its SHA-256 hash). Clients can send this identifier instead of the\ncorresponding query string, thus reducing request sizes dramatically (response sizes are unaffected)."),(0,s.yg)("p",null,"To persist a query string, GraphQLite server must first receive it from a requesting client.\nConsequently, each unique query string must be sent to Apollo Server at least once.\nAfter any client sends a query string to persist, every client that executes that query can then benefit from APQ."),(0,s.yg)(a.K,{chart:"sequenceDiagram;\n Client app->>GraphQL Server: Sends SHA-256 hash of query string to execute\n Note over GraphQL Server: Fails to find persisted query string\n GraphQL Server->>Client app: Responds with error\n Client app->>GraphQL Server: Sends both query string AND hash\n Note over GraphQL Server: Persists query string and hash\n GraphQL Server->>Client app: Executes query and returns result\n Note over Client app: Time passes\n Client app->>GraphQL Server: Sends SHA-256 hash of query string to execute\n Note over GraphQL Server: Finds persisted query string\n GraphQL Server->>Client app: Executes query and returns result",mdxType:"Mermaid"}),(0,s.yg)("p",null,"Persisted queries are especially effective when clients send queries as GET requests.\nThis enables clients to take advantage of the browser cache and integrate with a CDN."),(0,s.yg)("p",null,"Because query identifiers are deterministic hashes, clients can generate them at runtime. No additional build steps are required."),(0,s.yg)("h2",{id:"setup"},"Setup"),(0,s.yg)("p",null,"To use Automatic persisted queries with GraphQLite, you may use\n",(0,s.yg)("inlineCode",{parentName:"p"},"useAutomaticPersistedQueries")," method when building your PSR-15 middleware:"),(0,s.yg)("pre",null,(0,s.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n// You need to provide a PSR compatible cache and a TTL for queries. The best cache would be some kind\n// of in-memory cache with a limit on number of entries to make sure your cache can't be maliciously spammed with queries.\n$builder->useAutomaticPersistedQueries($cache, new DateInterval('PT1H'));\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[68],{95007:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>n,metadata:()=>u,toc:()=>d});var i=r(58168),s=(r(96540),r(15680)),a=r(67443);const n={id:"automatic-persisted-queries",title:"Automatic persisted queries",sidebar_label:"Automatic persisted queries"},o=void 0,u={unversionedId:"automatic-persisted-queries",id:"version-7.0.0/automatic-persisted-queries",title:"Automatic persisted queries",description:"The problem",source:"@site/versioned_docs/version-7.0.0/automatic-persisted-queries.mdx",sourceDirName:".",slug:"/automatic-persisted-queries",permalink:"/docs/automatic-persisted-queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/automatic-persisted-queries.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"automatic-persisted-queries",title:"Automatic persisted queries",sidebar_label:"Automatic persisted queries"},sidebar:"docs",previous:{title:"Prefetching records",permalink:"/docs/prefetch-method"},next:{title:"File uploads",permalink:"/docs/file-uploads"}},l={},d=[{value:"The problem",id:"the-problem",level:2},{value:"Apollo APQ",id:"apollo-apq",level:2},{value:"Setup",id:"setup",level:2}],p={toc:d},c="wrapper";function h(e){let{components:t,...r}=e;return(0,s.yg)(c,(0,i.A)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,s.yg)("h2",{id:"the-problem"},"The problem"),(0,s.yg)("p",null,"Clients send queries to GraphQLite as HTTP requests that include the GraphQL string of the query to execute.\nDepending on your graph's schema, the size of a valid query string might be arbitrarily large.\nAs query strings become larger, increased latency and network usage can noticeably degrade client performance."),(0,s.yg)("p",null,'To combat this, GraphQL servers use a technique called "persisted queries". The basic idea is instead of\nsending the whole query string, clients only send it\'s unique identifier. The server then finds the actual\nquery string by given identifier and use that as if the client sent the whole query in the first place.\nThat helps improve GraphQL network performance with zero build-time configuration by sending smaller GraphQL HTTP requests.\nA smaller request payload reduces bandwidth utilization and speeds up GraphQL Client loading times.'),(0,s.yg)("h2",{id:"apollo-apq"},"Apollo APQ"),(0,s.yg)("p",null,(0,s.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/apollo-server/performance/apq/"},"Automatic persisted queries (APQ) is technique created by Apollo"),"\nand is aimed to implement a simple automatic way of persisting queries. Queries are cached on the server side,\nalong with its unique identifier (always its SHA-256 hash). Clients can send this identifier instead of the\ncorresponding query string, thus reducing request sizes dramatically (response sizes are unaffected)."),(0,s.yg)("p",null,"To persist a query string, GraphQLite server must first receive it from a requesting client.\nConsequently, each unique query string must be sent to Apollo Server at least once.\nAfter any client sends a query string to persist, every client that executes that query can then benefit from APQ."),(0,s.yg)(a.K,{chart:"sequenceDiagram;\n Client app->>GraphQL Server: Sends SHA-256 hash of query string to execute\n Note over GraphQL Server: Fails to find persisted query string\n GraphQL Server->>Client app: Responds with error\n Client app->>GraphQL Server: Sends both query string AND hash\n Note over GraphQL Server: Persists query string and hash\n GraphQL Server->>Client app: Executes query and returns result\n Note over Client app: Time passes\n Client app->>GraphQL Server: Sends SHA-256 hash of query string to execute\n Note over GraphQL Server: Finds persisted query string\n GraphQL Server->>Client app: Executes query and returns result",mdxType:"Mermaid"}),(0,s.yg)("p",null,"Persisted queries are especially effective when clients send queries as GET requests.\nThis enables clients to take advantage of the browser cache and integrate with a CDN."),(0,s.yg)("p",null,"Because query identifiers are deterministic hashes, clients can generate them at runtime. No additional build steps are required."),(0,s.yg)("h2",{id:"setup"},"Setup"),(0,s.yg)("p",null,"To use Automatic persisted queries with GraphQLite, you may use\n",(0,s.yg)("inlineCode",{parentName:"p"},"useAutomaticPersistedQueries")," method when building your PSR-15 middleware:"),(0,s.yg)("pre",null,(0,s.yg)("code",{parentName:"pre",className:"language-php"},"$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n// You need to provide a PSR compatible cache and a TTL for queries. The best cache would be some kind\n// of in-memory cache with a limit on number of entries to make sure your cache can't be maliciously spammed with queries.\n$builder->useAutomaticPersistedQueries($cache, new DateInterval('PT1H'));\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a27f6be0.01e05b2b.js b/assets/js/a27f6be0.7a85335b.js similarity index 92% rename from assets/js/a27f6be0.01e05b2b.js rename to assets/js/a27f6be0.7a85335b.js index fc09151e3c..86c608cd02 100644 --- a/assets/js/a27f6be0.01e05b2b.js +++ b/assets/js/a27f6be0.7a85335b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1062],{78291:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>p,frontMatter:()=>a,metadata:()=>s,toc:()=>c});var i=n(58168),r=(n(96540),n(15680));n(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework",original_id:"implementing-security"},o=void 0,s={unversionedId:"implementing-security",id:"version-4.1/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package",source:"@site/versioned_docs/version-4.1/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/4.1/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/implementing-security.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework",original_id:"implementing-security"},sidebar:"version-4.1/docs",previous:{title:"Fine grained security",permalink:"/docs/4.1/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/4.1/query-plan"}},u={},c=[],l={toc:c},g="wrapper";function p(e){let{components:t,...n}=e;return(0,r.yg)(g,(0,i.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package"),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1062],{78291:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>p,frontMatter:()=>a,metadata:()=>s,toc:()=>c});var n=i(58168),r=(i(96540),i(15680));i(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework",original_id:"implementing-security"},o=void 0,s={unversionedId:"implementing-security",id:"version-4.1/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package",source:"@site/versioned_docs/version-4.1/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/4.1/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/implementing-security.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework",original_id:"implementing-security"},sidebar:"version-4.1/docs",previous:{title:"Fine grained security",permalink:"/docs/4.1/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/4.1/query-plan"}},u={},c=[],l={toc:c},g="wrapper";function p(e){let{components:t,...i}=e;return(0,r.yg)(g,(0,n.A)({},l,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package"),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a28aff23.d779452d.js b/assets/js/a28aff23.a5c896b5.js similarity index 94% rename from assets/js/a28aff23.d779452d.js rename to assets/js/a28aff23.a5c896b5.js index b7da693420..badd4d9a36 100644 --- a/assets/js/a28aff23.d779452d.js +++ b/assets/js/a28aff23.a5c896b5.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6337],{19365:(e,n,t)=>{t.d(n,{A:()=>o});var a=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>I});var a=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function p(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??d(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function h(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=g({queryString:t,groupId:a}),[d,m]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),y=(()=>{const e=u??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),m(e)}),[s,m,i]),tabValues:i}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(d(n),u(a))},h=e=>{let n=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function N(e){const n=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function I(e){const n=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(n)},e))}},32488:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>g,frontMatter:()=>l,metadata:()=>s,toc:()=>d});var a=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},u=void 0,s={unversionedId:"authentication-authorization",id:"version-6.0/authentication-authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-6.0/authentication-authorization.mdx",sourceDirName:".",slug:"/authentication-authorization",permalink:"/docs/6.0/authentication-authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/authentication-authorization.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},sidebar:"docs",previous:{title:"User input validation",permalink:"/docs/6.0/validation"},next:{title:"Fine grained security",permalink:"/docs/6.0/fine-grained-security"}},c={},d=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],p={toc:d},h="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(h,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/6.0/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6337],{19365:(e,n,t)=>{t.d(n,{A:()=>o});var a=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:n,hidden:t,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>I});var a=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function h(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??d(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function p(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=h(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!p({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=g({queryString:t,groupId:a}),[d,m]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),y=(()=>{const e=u??d;return p({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!p({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),m(e)}),[s,m,i]),tabValues:i}}var y=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(d(n),u(a))},p=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:p,onClick:h},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function N(e){const n=m(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function I(e){const n=(0,y.A)();return r.createElement(N,(0,a.A)({key:String(n)},e))}},32488:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>g,frontMatter:()=>l,metadata:()=>s,toc:()=>d});var a=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},u=void 0,s={unversionedId:"authentication-authorization",id:"version-6.0/authentication-authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-6.0/authentication-authorization.mdx",sourceDirName:".",slug:"/authentication-authorization",permalink:"/docs/6.0/authentication-authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/authentication-authorization.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"authentication-authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization"},sidebar:"docs",previous:{title:"User input validation",permalink:"/docs/6.0/validation"},next:{title:"Fine grained security",permalink:"/docs/6.0/fine-grained-security"}},c={},d=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],h={toc:d},p="wrapper";function g(e){let{components:n,...t}=e;return(0,r.yg)(p,(0,a.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,r.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,r.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,r.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,r.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,r.yg)("a",{parentName:"li",href:"/docs/6.0/fine-grained-security"},(0,r.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,r.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,r.yg)("br",null),"See ",(0,r.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,r.yg)("h2",{id:"logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,r.yg)("p",null,"GraphQLite exposes two annotations (",(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"In the example above, the query ",(0,r.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,r.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,r.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,r.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[FailWith(value: null)]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @return Product\n */\n public function product(\n int $id,\n #[InjectUser]\n User $user\n ): Product\n {\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,r.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,r.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,r.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,r.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @return User[]\n */\n #[Query]\n #[Logged]\n #[Right("CAN_VIEW_USER_LIST")]\n #[HideIfUnauthorized]\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,r.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a2d3d8d2.6d97a807.js b/assets/js/a2d3d8d2.02d26f89.js similarity index 98% rename from assets/js/a2d3d8d2.6d97a807.js rename to assets/js/a2d3d8d2.02d26f89.js index b3af0ea0a9..b6c82229a6 100644 --- a/assets/js/a2d3d8d2.6d97a807.js +++ b/assets/js/a2d3d8d2.02d26f89.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6123],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},59490:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},u=void 0,s={unversionedId:"extend-input-type",id:"version-6.0/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.0/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/6.0/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/extend-input-type.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"docs",previous:{title:"Custom argument resolving",permalink:"/docs/6.0/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/6.0/multiple-output-types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6123],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},59490:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},u=void 0,s={unversionedId:"extend-input-type",id:"version-6.0/extend-input-type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.0/extend-input-type.mdx",sourceDirName:".",slug:"/extend-input-type",permalink:"/docs/6.0/extend-input-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/extend-input-type.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-input-type",title:"Extending an input type",sidebar_label:"Extending an input type"},sidebar:"docs",previous:{title:"Custom argument resolving",permalink:"/docs/6.0/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/6.0/multiple-output-types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/6.0/extend-type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a30fd8ca.cc2ee382.js b/assets/js/a30fd8ca.a1a62e13.js similarity index 98% rename from assets/js/a30fd8ca.cc2ee382.js rename to assets/js/a30fd8ca.a1a62e13.js index d49f1a452f..03cafa1a9e 100644 --- a/assets/js/a30fd8ca.cc2ee382.js +++ b/assets/js/a30fd8ca.a1a62e13.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[501],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},22061:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend_input_type",title:"Extending an input type",sidebar_label:"Extending an input type",original_id:"extend_input_type"},u=void 0,s={unversionedId:"extend_input_type",id:"version-4.1/extend_input_type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.1/extend_input_type.mdx",sourceDirName:".",slug:"/extend_input_type",permalink:"/docs/4.1/extend_input_type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/extend_input_type.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend_input_type",title:"Extending an input type",sidebar_label:"Extending an input type",original_id:"extend_input_type"},sidebar:"version-4.1/docs",previous:{title:"Custom argument resolving",permalink:"/docs/4.1/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/4.1/multiple_output_types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/extend_type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[501],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>x});var n=a(58168),r=a(96540),l=a(20053),i=a(23104),o=a(56347),u=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,u.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:a,groupId:n}),[c,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=u??c;return y({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&o(f)}),[f]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!y({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),h(e)}),[s,h,l]),tabValues:l}}var f=a(92303);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:o,selectValue:u,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==o&&(c(t),u(n))},y=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:i}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:y,onClick:d},i,{className:(0,l.A)("tabs__item",g.tabItem,i?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",g.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function x(e){const t=(0,f.A)();return r.createElement(T,(0,n.A)({key:String(t)},e))}},22061:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>u,default:()=>m,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const o={id:"extend_input_type",title:"Extending an input type",sidebar_label:"Extending an input type",original_id:"extend_input_type"},u=void 0,s={unversionedId:"extend_input_type",id:"version-4.1/extend_input_type",title:"Extending an input type",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.1/extend_input_type.mdx",sourceDirName:".",slug:"/extend_input_type",permalink:"/docs/4.1/extend_input_type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/extend_input_type.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend_input_type",title:"Extending an input type",sidebar_label:"Extending an input type",original_id:"extend_input_type"},sidebar:"version-4.1/docs",previous:{title:"Custom argument resolving",permalink:"/docs/4.1/argument-resolving"},next:{title:"Class with multiple output types",permalink:"/docs/4.1/multiple_output_types"}},p={},c=[],d={toc:c},y="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(y,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("div",{class:"alert alert--info"},"If you are not familiar with the ",(0,r.yg)("code",null,"@Factory")," tag, ",(0,r.yg)("a",{href:"input-types"},'read first the "input types" guide'),"."),(0,r.yg)("p",null,"Fields exposed in a GraphQL input type do not need to be all part of the factory method."),(0,r.yg)("p",null,"Just like with output type (that can be ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/extend_type"},"extended using the ",(0,r.yg)("inlineCode",{parentName:"a"},"ExtendType")," annotation"),"), you can extend/modify\nan input type using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation to add additional fields to an input type that is already declared by a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation,\nor to modify the returned object."),(0,r.yg)("div",{class:"alert alert--info"},"The ",(0,r.yg)("code",null,"@Decorate")," annotation is very useful in scenarios where you cannot touch the ",(0,r.yg)("code",null,"@Factory")," method. This can happen if the ",(0,r.yg)("code",null,"@Factory")," method is defined in a third-party library or if the ",(0,r.yg)("code",null,"@Factory")," method is part of auto-generated code."),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Filter")," class used as an input type. You most certainly have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Factory")," to create the input type."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n #[Factory]\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * @Factory()\n */\n public function createFilter(string $name): Filter\n {\n // Let's assume you have a flexible 'Filter' class that can accept any kind of filter\n $filter = new Filter();\n $filter->addFilter('name', $name);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,"Assuming you ",(0,r.yg)("strong",{parentName:"p"},"cannot"),' modify the code of this factory, you can still modify the GraphQL input type generated by\nadding a "decorator" around the factory.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n #[Decorate(inputTypeName: \"FilterInput\")]\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyDecorator\n{\n /**\n * @Decorate(inputTypeName=\"FilterInput\")\n */\n public function addTypeFilter(Filter $filter, string $type): Filter\n {\n $filter->addFilter('type', $type);\n return $filter;\n }\n}\n")))),(0,r.yg)("p",null,'In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.'),(0,r.yg)("p",null,"A few things to notice:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The decorator takes the object generated by the factory as first argument"),(0,r.yg)("li",{parentName:"ul"},"The decorator MUST return an object of the same type (or a sub-type)"),(0,r.yg)("li",{parentName:"ul"},"The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"@Decorate")," annotation must contain a ",(0,r.yg)("inlineCode",{parentName:"li"},"inputTypeName")," attribute that contains the name of the GraphQL input type\nthat is decorated. If you did not specify this name in the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Factory"),' annotation, this is by default the name of the\nPHP class + "Input" (for instance: "Filter" => "FilterInput")')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"MyDecorator")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a320b509.542f12dd.js b/assets/js/a320b509.90fae613.js similarity index 98% rename from assets/js/a320b509.542f12dd.js rename to assets/js/a320b509.90fae613.js index 806be57583..7b8f93801d 100644 --- a/assets/js/a320b509.542f12dd.js +++ b/assets/js/a320b509.90fae613.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4779],{87719:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var i=t(58168),a=(t(96540),t(15680));t(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},r=void 0,o={unversionedId:"field-middlewares",id:"version-3.0/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-3.0/field_middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/3.0/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/field_middlewares.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour\nof a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:t(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library (for PHP 7+) and/or by PHP 8 attributes."),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"OnlyDebug.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface\nis a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation\nis to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:",(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"li"},"graphql.field_middleware")," tag.")))}c.isMDXComponent=!0},8643:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4779],{87719:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var i=t(58168),a=(t(96540),t(15680));t(67443);const l={id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"},r=void 0,o={unversionedId:"field-middlewares",id:"version-3.0/field-middlewares",title:"Adding custom annotations with Field middlewares",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-3.0/field_middlewares.md",sourceDirName:".",slug:"/field-middlewares",permalink:"/docs/3.0/field-middlewares",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/field_middlewares.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"field-middlewares",title:"Adding custom annotations with Field middlewares",sidebar_label:"Custom annotations"}},d={},s=[{value:"Field middlewares",id:"field-middlewares",level:2},{value:"Annotations parsing",id:"annotations-parsing",level:2}],u={toc:s},p="wrapper";function c(e){let{components:n,...l}=e;return(0,a.yg)(p,(0,i.A)({},u,l,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("small",null,"Available in GraphQLite 4.0+"),(0,a.yg)("p",null,"Just like the ",(0,a.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,a.yg)("inlineCode",{parentName:"p"},"@Right")," annotation, you can develop your own annotation that extends/modifies the behaviour\nof a field/query/mutation."),(0,a.yg)("div",{class:"alert alert--warning"},"If you want to create an annotation that targets a single argument (like ",(0,a.yg)("code",null,'@AutoWire(for="$service")'),"), you should rather check the documentation about ",(0,a.yg)("a",{href:"argument-resolving"},"custom argument resolving")),(0,a.yg)("h2",{id:"field-middlewares"},"Field middlewares"),(0,a.yg)("p",null,"GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition")," class.\nIn order to create a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),' instance for your field, GraphQLite goes through a series of "middlewares".'),(0,a.yg)("p",null,(0,a.yg)("img",{src:t(8643).A,width:"960",height:"540"})),(0,a.yg)("p",null,"Each middleware is passed a ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\QueryFieldDescriptor")," instance. This object contains all the\nparameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)"),(0,a.yg)("p",null,"Each middleware must return a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\FieldDefinition")," (the object representing a field in Webonyx/GraphQL-PHP)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Your middleware must implement this interface.\n */\ninterface FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;\n}\n")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"class QueryFieldDescriptor\n{\n public function getName() { /* ... */ }\n public function setName(string $name) { /* ... */ }\n public function getType() { /* ... */ }\n public function setType($type): void { /* ... */ }\n public function getParameters(): array { /* ... */ }\n public function setParameters(array $parameters): void { /* ... */ }\n public function getPrefetchParameters(): array { /* ... */ }\n public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }\n public function getPrefetchMethodName(): ?string { /* ... */ }\n public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }\n public function setCallable(callable $callable): void { /* ... */ }\n public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }\n public function isInjectSource(): bool { /* ... */ }\n public function setInjectSource(bool $injectSource): void { /* ... */ }\n public function getComment(): ?string { /* ... */ }\n public function setComment(?string $comment): void { /* ... */ }\n public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }\n public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }\n public function getOriginalResolver(): ResolverInterface { /* ... */ }\n public function getResolver(): callable { /* ... */ }\n public function setResolver(callable $resolver): void { /* ... */ }\n}\n")),(0,a.yg)("p",null,"The role of a middleware is to analyze the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor")," and modify it (or to directly return a ",(0,a.yg)("inlineCode",{parentName:"p"},"FieldDefinition"),")."),(0,a.yg)("p",null,"If you want the field to purely disappear, your middleware can return ",(0,a.yg)("inlineCode",{parentName:"p"},"null"),"."),(0,a.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,a.yg)("p",null,"Take a look at the ",(0,a.yg)("inlineCode",{parentName:"p"},"QueryFieldDescriptor::getMiddlewareAnnotations()"),"."),(0,a.yg)("p",null,"It returns the list of annotations applied to your field that implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),"."),(0,a.yg)("p",null,"Let's imagine you want to add a ",(0,a.yg)("inlineCode",{parentName:"p"},"@OnlyDebug")," annotation that displays a field/query/mutation only in debug mode (and\nhides the field in production). That could be useful, right?"),(0,a.yg)("p",null,"First, we have to define the annotation. Annotations are handled by the great ",(0,a.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html"},"doctrine/annotations")," library (for PHP 7+) and/or by PHP 8 attributes."),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"OnlyDebug.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Annotations;\n\nuse Attribute;\nuse TheCodingMachine\\GraphQLite\\Annotations\\MiddlewareAnnotationInterface;\n\n/**\n * @Annotation\n * @Target({"METHOD", "ANNOTATION"})\n */\n#[Attribute(Attribute::TARGET_METHOD)]\nclass OnlyDebug implements MiddlewareAnnotationInterface\n{\n}\n')),(0,a.yg)("p",null,"Apart from being a classical annotation/attribute, this class implements the ",(0,a.yg)("inlineCode",{parentName:"p"},"MiddlewareAnnotationInterface"),'. This interface\nis a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation\nis to be used by middlewares.'),(0,a.yg)("p",null,"Now, we can write a middleware that will act upon this annotation."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Middlewares;\n\nuse App\\Annotations\\OnlyDebug;\nuse TheCodingMachine\\GraphQLite\\Middlewares\\FieldMiddlewareInterface;\nuse GraphQL\\Type\\Definition\\FieldDefinition;\nuse TheCodingMachine\\GraphQLite\\QueryFieldDescriptor;\n\n/**\n * Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set\n */\nclass OnlyDebugFieldMiddleware implements FieldMiddlewareInterface\n{\n public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition\n {\n $annotations = $queryFieldDescriptor->getMiddlewareAnnotations();\n\n /**\n * @var OnlyDebug $onlyDebug\n */\n $onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);\n\n if ($onlyDebug !== null && !DEBUG) {\n // If the onlyDebug annotation is present, returns null.\n // Returning null will hide the field.\n return null;\n }\n\n // Otherwise, let's continue the middleware pipe without touching anything.\n return $fieldHandler->handle($queryFieldDescriptor);\n }\n}\n")),(0,a.yg)("p",null,"The final thing we have to do is to register the middleware."),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Assuming you are using the ",(0,a.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to initialize GraphQLite, you can register the field middleware using:",(0,a.yg)("pre",{parentName:"li"},(0,a.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());\n"))),(0,a.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, you can register your field middleware services by tagging them with the ",(0,a.yg)("inlineCode",{parentName:"li"},"graphql.field_middleware")," tag.")))}c.isMDXComponent=!0},8643:(e,n,t)=>{t.d(n,{A:()=>i});const i=t.p+"assets/images/field_middleware-5c3e3b4da480c49d048d527f93cc970d.svg"}}]); \ No newline at end of file diff --git a/assets/js/a3a193a6.2bb72676.js b/assets/js/a3a193a6.8bfca0ec.js similarity index 92% rename from assets/js/a3a193a6.2bb72676.js rename to assets/js/a3a193a6.8bfca0ec.js index 5d496b2664..a36f575c6a 100644 --- a/assets/js/a3a193a6.2bb72676.js +++ b/assets/js/a3a193a6.8bfca0ec.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[464],{7254:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning",original_id:"semver"},o=void 0,s={unversionedId:"semver",id:"version-4.0/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all",source:"@site/versioned_docs/version-4.0/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/4.0/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/semver.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning",original_id:"semver"},sidebar:"version-4.0/docs",previous:{title:"Annotations reference",permalink:"/docs/4.0/annotations_reference"},next:{title:"Changelog",permalink:"/docs/4.0/changelog"}},l={},p=[],m={toc:p},d="wrapper";function u(e){let{components:a,...t}=e;return(0,n.yg)(d,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all\nminor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short,\nSemantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility.\nMinor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of\nthat release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental"),"\nand their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a major version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a minor version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library.\nThey are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[464],{7254:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var r=t(58168),n=(t(96540),t(15680));t(67443);const i={id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning",original_id:"semver"},o=void 0,s={unversionedId:"semver",id:"version-4.0/semver",title:"Our backward compatibility promise",description:"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all",source:"@site/versioned_docs/version-4.0/semver.md",sourceDirName:".",slug:"/semver",permalink:"/docs/4.0/semver",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/semver.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"semver",title:"Our backward compatibility promise",sidebar_label:"Semantic versioning",original_id:"semver"},sidebar:"version-4.0/docs",previous:{title:"Annotations reference",permalink:"/docs/4.0/annotations_reference"},next:{title:"Changelog",permalink:"/docs/4.0/changelog"}},l={},p=[],m={toc:p},u="wrapper";function d(e){let{components:a,...t}=e;return(0,n.yg)(u,(0,r.A)({},m,t,{components:a,mdxType:"MDXLayout"}),(0,n.yg)("p",null,"Ensuring smooth upgrades of your project is a priority. That's why we promise you backward compatibility (BC) for all\nminor GraphQLite releases. You probably recognize this strategy as ",(0,n.yg)("a",{parentName:"p",href:"https://semver.org/"},"Semantic Versioning"),". In short,\nSemantic Versioning means that only major releases (such as 4.0, 5.0 etc.) are allowed to break backward compatibility.\nMinor releases (such as 4.0, 4.1 etc.) may introduce new features, but must do so without breaking the existing API of\nthat release branch (4.x in the previous example)."),(0,n.yg)("p",null,'But sometimes, a new feature is not quite "dry" and we need a bit of time to find the perfect API.\nIn such cases, we prefer to gather feedback from real-world usage, adapt the API, or remove it altogether.\nDoing so is not possible with a no BC-break approach.'),(0,n.yg)("p",null,"To avoid being bound to our backward compatibility promise, such features can be marked as ",(0,n.yg)("strong",{parentName:"p"},"unstable")," or ",(0,n.yg)("strong",{parentName:"p"},"experimental"),"\nand their classes and methods are marked with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," tag."),(0,n.yg)("p",null,(0,n.yg)("inlineCode",{parentName:"p"},"@unstable")," or ",(0,n.yg)("inlineCode",{parentName:"p"},"@experimental")," classes / methods will ",(0,n.yg)("strong",{parentName:"p"},"not break")," in a patch release, but ",(0,n.yg)("em",{parentName:"p"},"may be broken")," in a minor version."),(0,n.yg)("p",null,"As a rule of thumb:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user (using GraphQLite mainly through its annotations), we guarantee strict semantic versioning"),(0,n.yg)("li",{parentName:"ul"},"If you are extending GraphQLite features (if you are developing custom annotations, or if you are developing a GraphQlite integration\nwith a framework...), be sure to check the tags.")),(0,n.yg)("p",null,"Said otherwise:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"If you are a GraphQLite user, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a major version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "^4"\n }\n}\n'))),(0,n.yg)("li",{parentName:"ul"},"If you are extending the GraphQLite ecosystem, in your ",(0,n.yg)("inlineCode",{parentName:"li"},"composer.json"),", target a minor version:",(0,n.yg)("pre",{parentName:"li"},(0,n.yg)("code",{parentName:"pre",className:"language-json"},'{\n "require": {\n "thecodingmachine/graphqlite": "~4.1.0"\n }\n}\n')))),(0,n.yg)("p",null,"Finally, classes / methods annotated with the ",(0,n.yg)("inlineCode",{parentName:"p"},"@internal")," annotation are not meant to be used in your code or third-party library.\nThey are meant for GraphQLite internal usage and they may break anytime. Do not use those directly."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a55b0daf.2eec40d4.js b/assets/js/a55b0daf.d34ec43f.js similarity index 99% rename from assets/js/a55b0daf.2eec40d4.js rename to assets/js/a55b0daf.d34ec43f.js index 7f73f373d4..9c96de5f9e 100644 --- a/assets/js/a55b0daf.2eec40d4.js +++ b/assets/js/a55b0daf.d34ec43f.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9441],{26884:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>l,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var r=a(58168),t=(a(96540),a(15680));a(67443);const o={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},i=void 0,s={unversionedId:"other-frameworks",id:"other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/docs/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/next/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/other-frameworks.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"docs",previous:{title:"Universal service providers",permalink:"/docs/next/universal-service-providers"},next:{title:"Queries",permalink:"/docs/next/queries"}},l={},p=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Disabling autoloading",id:"disabling-autoloading",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],c={toc:p},d="wrapper";function u(e){let{components:n,...a}=e;return(0,t.yg)(d,(0,r.A)({},c,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers')\n ->addTypeNamespace('App');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the #[Logged] attributes).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the #[Right] attributes).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Set a default InputType validator service to handle validation on all `Input` annotated types\n$factory->setInputTypeValidator($validator);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h3",{id:"disabling-autoloading"},"Disabling autoloading"),(0,t.yg)("p",null,"GraphQLite uses ",(0,t.yg)("inlineCode",{parentName:"p"},"kcs/class-finder")," to find all classes that have GraphQLite attributes. By default, it uses\nautoloading under the hood. But if you have an older codebase that contains classes with incorrect or missing\nnamespaces, you may need to use ",(0,t.yg)("inlineCode",{parentName:"p"},"include_once")," instead. To do so, you can overwrite the finder using ",(0,t.yg)("inlineCode",{parentName:"p"},"setFinder()"),":"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use Kcs\\ClassFinder\\Finder\\ComposerFinder;\nuse TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers')\n ->addTypeNamespace('App')\n ->setFinder(\n (new ComposerFinder())->useAutoloading(false)\n );\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers')\n ->addTypeNamespace('App');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"$builder->setUrl(\"/graphql\"); // Modify the URL endpoint (defaults to /graphql)\n\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object.\n// Define your own formatter and error handlers for Webonyx.\n$config->setErrorFormatter([ExceptionHandler::class, 'errorFormatter']);\n$config->setErrorsHandler([ExceptionHandler::class, 'errorHandler']);\n\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n\n// Configure the server to use Apollo automatic persisted queries with given cache and an optional time-to-live.\n// See https://www.apollographql.com/docs/apollo-server/performance/apq/\n$builder->useAutomaticPersistedQueries($cache, new DateInterval('PT1H'));\n")),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.laminas.dev/laminas-stratigility/"},"Laminas Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "laminas/laminas-diactoros": "^2",\n "laminas/laminas-stratigility": "^3",\n "laminas/laminas-httphandlerrunner": "^2",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Laminas ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Laminas Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n // Any PSR-16 cache should work - APCu is recommended for good\n // performance, but it requires that module to be enabled. For\n // small scale testing with zero dependencies, FilesystemCache\n // can be used instead.\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers');\n $factory->addTypeNamespace('App');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9441],{26884:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>l,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var r=a(58168),t=(a(96540),a(15680));a(67443);const o={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},i=void 0,s={unversionedId:"other-frameworks",id:"other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/docs/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/next/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/other-frameworks.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"docs",previous:{title:"Universal service providers",permalink:"/docs/next/universal-service-providers"},next:{title:"Queries",permalink:"/docs/next/queries"}},l={},p=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Disabling autoloading",id:"disabling-autoloading",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],c={toc:p},d="wrapper";function u(e){let{components:n,...a}=e;return(0,t.yg)(d,(0,r.A)({},c,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers')\n ->addTypeNamespace('App');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the #[Logged] attributes).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the #[Right] attributes).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Set a default InputType validator service to handle validation on all `Input` annotated types\n$factory->setInputTypeValidator($validator);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h3",{id:"disabling-autoloading"},"Disabling autoloading"),(0,t.yg)("p",null,"GraphQLite uses ",(0,t.yg)("inlineCode",{parentName:"p"},"kcs/class-finder")," to find all classes that have GraphQLite attributes. By default, it uses\nautoloading under the hood. But if you have an older codebase that contains classes with incorrect or missing\nnamespaces, you may need to use ",(0,t.yg)("inlineCode",{parentName:"p"},"include_once")," instead. To do so, you can overwrite the finder using ",(0,t.yg)("inlineCode",{parentName:"p"},"setFinder()"),":"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use Kcs\\ClassFinder\\Finder\\ComposerFinder;\nuse TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers')\n ->addTypeNamespace('App')\n ->setFinder(\n (new ComposerFinder())->useAutoloading(false)\n );\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers')\n ->addTypeNamespace('App');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"$builder->setUrl(\"/graphql\"); // Modify the URL endpoint (defaults to /graphql)\n\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object.\n// Define your own formatter and error handlers for Webonyx.\n$config->setErrorFormatter([ExceptionHandler::class, 'errorFormatter']);\n$config->setErrorsHandler([ExceptionHandler::class, 'errorHandler']);\n\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n\n// Configure the server to use Apollo automatic persisted queries with given cache and an optional time-to-live.\n// See https://www.apollographql.com/docs/apollo-server/performance/apq/\n$builder->useAutomaticPersistedQueries($cache, new DateInterval('PT1H'));\n")),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.laminas.dev/laminas-stratigility/"},"Laminas Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "laminas/laminas-diactoros": "^2",\n "laminas/laminas-stratigility": "^3",\n "laminas/laminas-httphandlerrunner": "^2",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Laminas ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Laminas Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n // Any PSR-16 cache should work - APCu is recommended for good\n // performance, but it requires that module to be enabled. For\n // small scale testing with zero dependencies, FilesystemCache\n // can be used instead.\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers');\n $factory->addTypeNamespace('App');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a9125b44.b87a4fc9.js b/assets/js/a9125b44.9d1e548e.js similarity index 99% rename from assets/js/a9125b44.b87a4fc9.js rename to assets/js/a9125b44.9d1e548e.js index ae65a11395..7a6e4f766f 100644 --- a/assets/js/a9125b44.b87a4fc9.js +++ b/assets/js/a9125b44.9d1e548e.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2301],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),i=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},50257:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const i={id:"queries",title:"Queries",sidebar_label:"Queries"},s=void 0,u={unversionedId:"queries",id:"version-7.0.0/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-7.0.0/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/queries.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/other-frameworks"},next:{title:"Mutations",permalink:"/docs/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...i}=e;return(0,r.yg)(h,(0,a.A)({},d,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2301],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),i=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==i&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},50257:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const i={id:"queries",title:"Queries",sidebar_label:"Queries"},s=void 0,u={unversionedId:"queries",id:"version-7.0.0/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-7.0.0/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/queries.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries"},sidebar:"docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/other-frameworks"},next:{title:"Mutations",permalink:"/docs/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...i}=e;return(0,r.yg)(h,(0,a.A)({},d,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file diff --git a/assets/js/a91c1a62.c5c73dcf.js b/assets/js/a91c1a62.f4487fce.js similarity index 89% rename from assets/js/a91c1a62.c5c73dcf.js rename to assets/js/a91c1a62.f4487fce.js index 38ee40e159..7fc9e8bfe5 100644 --- a/assets/js/a91c1a62.c5c73dcf.js +++ b/assets/js/a91c1a62.f4487fce.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6219],{89813:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>d,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-7.0.0/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-7.0.0/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/troubleshooting.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"docs",previous:{title:"Internals",permalink:"/docs/internals"},next:{title:"Migrating",permalink:"/docs/migrating"}},l={},u=[],p={toc:u},c="wrapper";function d(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,n.A)({},p,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6219],{89813:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>g,frontMatter:()=>i,metadata:()=>a,toc:()=>u});var n=o(58168),r=(o(96540),o(15680));o(67443);const i={id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},s=void 0,a={unversionedId:"troubleshooting",id:"version-7.0.0/troubleshooting",title:"Troubleshooting",description:"Error: Maximum function nesting level of '100' reached",source:"@site/versioned_docs/version-7.0.0/troubleshooting.md",sourceDirName:".",slug:"/troubleshooting",permalink:"/docs/troubleshooting",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/troubleshooting.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"troubleshooting",title:"Troubleshooting",sidebar_label:"Troubleshooting"},sidebar:"docs",previous:{title:"Internals",permalink:"/docs/internals"},next:{title:"Migrating",permalink:"/docs/migrating"}},l={},u=[],p={toc:u},c="wrapper";function g(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,n.A)({},p,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Error: Maximum function nesting level of '100' reached")),(0,r.yg)("p",null,"Webonyx's GraphQL library tends to use a very deep stack.\nThis error does not necessarily mean your code is going into an infinite loop.\nSimply try to increase the maximum allowed nesting level in your XDebug conf:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"xdebug.max_nesting_level=500\n")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},'Cannot autowire service "',(0,r.yg)("em",{parentName:"strong"},"[some input type]"),'": argument "$..." of method "..." is type-hinted "...", you should configure its value explicitly.')),(0,r.yg)("p",null,"The message says that Symfony is trying to instantiate an input type as a service. This can happen if you put your\nGraphQLite controllers in the Symfony controller namespace (",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default). Symfony will assume that any\nobject type-hinted in a method of a controller is a service (",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services"},'because all controllers are tagged with the "controller.service_arguments" tag'),")"),(0,r.yg)("p",null,"To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and\nreconfigure your ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.yml")," file to point to your new namespace."))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a95c9e82.d7bf2a67.js b/assets/js/a95c9e82.600fb4a5.js similarity index 98% rename from assets/js/a95c9e82.d7bf2a67.js rename to assets/js/a95c9e82.600fb4a5.js index ed52441543..65b621dd51 100644 --- a/assets/js/a95c9e82.d7bf2a67.js +++ b/assets/js/a95c9e82.600fb4a5.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8309],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const o={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),o=a(20053),u=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=p(e),[u,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,o]),tabValues:o}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,o.A)("tabs__item",v.tabItem,u?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function T(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},40963:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),o=(a(67443),a(11470)),u=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,i={unversionedId:"mutations",id:"version-4.3/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-4.3/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/4.3/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/mutations.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"version-4.3/docs",previous:{title:"Queries",permalink:"/docs/4.3/queries"},next:{title:"Type mapping",permalink:"/docs/4.3/type-mapping"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8309],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const o={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),o=a(20053),u=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=p(e),[u,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,o]),tabValues:o}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,o.A)("tabs__item",v.tabItem,u?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function T(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},40963:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),o=(a(67443),a(11470)),u=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,i={unversionedId:"mutations",id:"version-4.3/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-4.3/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/4.3/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/mutations.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"version-4.3/docs",previous:{title:"Queries",permalink:"/docs/4.3/queries"},next:{title:"Type mapping",permalink:"/docs/4.3/type-mapping"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a99e9943.861d4b51.js b/assets/js/a99e9943.9c2c1474.js similarity index 89% rename from assets/js/a99e9943.861d4b51.js rename to assets/js/a99e9943.9c2c1474.js index 7e05aa230c..70405b54dc 100644 --- a/assets/js/a99e9943.861d4b51.js +++ b/assets/js/a99e9943.9c2c1474.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9866],{91357:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>u});var i=n(58168),r=(n(96540),n(15680));n(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-4.3/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories.",source:"@site/versioned_docs/version-4.3/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/4.3/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/implementing-security.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},sidebar:"version-4.3/docs",previous:{title:"Fine grained security",permalink:"/docs/4.3/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/4.3/query-plan"}},c={},u=[],l={toc:u},p="wrapper";function h(e){let{components:t,...n}=e;return(0,r.yg)(p,(0,i.A)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories."),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9866],{91357:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),r=(i(96540),i(15680));i(67443);const a={id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},o=void 0,s={unversionedId:"implementing-security",id:"version-4.3/implementing-security",title:"Connecting GraphQLite to your framework's security module",description:"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories.",source:"@site/versioned_docs/version-4.3/implementing-security.md",sourceDirName:".",slug:"/implementing-security",permalink:"/docs/4.3/implementing-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/implementing-security.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"implementing-security",title:"Connecting GraphQLite to your framework's security module",sidebar_label:"Connecting security to your framework"},sidebar:"version-4.3/docs",previous:{title:"Fine grained security",permalink:"/docs/4.3/fine-grained-security"},next:{title:"Query plan",permalink:"/docs/4.3/query-plan"}},c={},l=[],u={toc:l},p="wrapper";function h(e){let{components:t,...i}=e;return(0,r.yg)(p,(0,n.A)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--info"},"At the time of writing, the Symfony Bundle and the Laravel package handle this implementation. For the latest documentation, please see their respective Github repositories."),(0,r.yg)("p",null,"GraphQLite needs to know if a user is logged or not, and what rights it has.\nBut this is specific of the framework you use."),(0,r.yg)("p",null,"To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthenticationServiceInterface")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Security\\AuthorizationServiceInterface"))),(0,r.yg)("p",null,"Those two interfaces act as adapters between GraphQLite and your framework:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthenticationServiceInterface\n{\n /**\n * Returns true if the "current" user is logged\n */\n public function isLogged(): bool;\n\n /**\n * Returns an object representing the current logged user.\n * Can return null if the user is not logged.\n */\n public function getUser(): ?object;\n}\n')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'interface AuthorizationServiceInterface\n{\n /**\n * Returns true if the "current" user has access to the right "$right"\n *\n * @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.\n */\n public function isAllowed(string $right, $subject = null): bool;\n}\n')),(0,r.yg)("p",null,"You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.\nIt you are ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/other-frameworks"},"using the ",(0,r.yg)("inlineCode",{parentName:"a"},"SchemaFactory")),", you can register your classes using:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Configure an authentication service (to resolve the @Logged annotations).\n$schemaFactory->setAuthenticationService($myAuthenticationService);\n// Configure an authorization service (to resolve the @Right annotations).\n$schemaFactory->setAuthorizationService($myAuthorizationService);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a9bc4f03.250477a2.js b/assets/js/a9bc4f03.b3e58655.js similarity index 98% rename from assets/js/a9bc4f03.250477a2.js rename to assets/js/a9bc4f03.b3e58655.js index df1d4edbca..719f4187b2 100644 --- a/assets/js/a9bc4f03.250477a2.js +++ b/assets/js/a9bc4f03.b3e58655.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[629],{14711:(a,e,t)=>{t.r(e),t.d(e,{assets:()=>s,contentTitle:()=>r,default:()=>y,frontMatter:()=>o,metadata:()=>l,toc:()=>d});var n=t(58168),i=(t(96540),t(15680));t(67443);const o={id:"validation",title:"Validation",sidebar_label:"User input validation",original_id:"validation"},r=void 0,l={unversionedId:"validation",id:"version-4.0/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-4.0/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/4.0/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/validation.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation",original_id:"validation"},sidebar:"version-4.0/docs",previous:{title:"Error handling",permalink:"/docs/4.0/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/4.0/authentication_authorization"}},s={},d=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],u={toc:d},p="wrapper";function y(a){let{components:e,...t}=a;return(0,i.yg)(p,(0,n.A)({},u,t,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,i.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,i.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,i.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,i.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,i.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box"),(0,i.yg)("li",{parentName:"ul"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,i.yg)("em",{parentName:"li"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:",(0,i.yg)("pre",{parentName:"li"},(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,i.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,i.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,i.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"UserController.php")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")),(0,i.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"User.php")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')),(0,i.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,i.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,i.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,i.yg)("p",null,"If the data entered by the user is ",(0,i.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,i.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\Graphqlite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,i.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,i.yg)("p",null,"You can also pass an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[629],{14711:(a,e,t)=>{t.r(e),t.d(e,{assets:()=>s,contentTitle:()=>r,default:()=>y,frontMatter:()=>o,metadata:()=>l,toc:()=>d});var n=t(58168),i=(t(96540),t(15680));t(67443);const o={id:"validation",title:"Validation",sidebar_label:"User input validation",original_id:"validation"},r=void 0,l={unversionedId:"validation",id:"version-4.0/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-4.0/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/4.0/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/validation.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation",original_id:"validation"},sidebar:"version-4.0/docs",previous:{title:"Error handling",permalink:"/docs/4.0/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/4.0/authentication_authorization"}},s={},d=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],u={toc:d},p="wrapper";function y(a){let{components:e,...t}=a;return(0,i.yg)(p,(0,n.A)({},u,t,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,i.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,i.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,i.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,i.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,i.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box"),(0,i.yg)("li",{parentName:"ul"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,i.yg)("em",{parentName:"li"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:",(0,i.yg)("pre",{parentName:"li"},(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,i.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,i.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,i.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"UserController.php")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")),(0,i.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"User.php")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')),(0,i.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,i.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,i.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,i.yg)("p",null,"If the data entered by the user is ",(0,i.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,i.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\Graphqlite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,i.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,i.yg)("p",null,"You can also pass an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/aa52484c.045d5777.js b/assets/js/aa52484c.61b177fd.js similarity index 96% rename from assets/js/aa52484c.045d5777.js rename to assets/js/aa52484c.61b177fd.js index 33182b5c9b..a86adf07a6 100644 --- a/assets/js/aa52484c.045d5777.js +++ b/assets/js/aa52484c.61b177fd.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4919],{18328:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>t,metadata:()=>s,toc:()=>l});var n=i(58168),a=(i(96540),i(15680));i(67443);const t={id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},o=void 0,s={unversionedId:"universal_service_providers",id:"version-4.0/universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-4.0/universal_service_providers.md",sourceDirName:".",slug:"/universal_service_providers",permalink:"/docs/4.0/universal_service_providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/universal_service_providers.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},sidebar:"version-4.0/docs",previous:{title:"Laravel package",permalink:"/docs/4.0/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/4.0/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function h(e){let{components:r,...i}=e;return(0,a.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,a.yg)("p",null,"If your framework is compatible with ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,a.yg)("h2",{id:"installation"},"Installation"),(0,a.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,a.yg)("h2",{id:"requirements"},"Requirements"),(0,a.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,a.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,a.yg)("p",null,"GraphQLite relies on the ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,a.yg)("a",{parentName:"p",href:"/docs/4.0/other-frameworks"},"PSR-15 middleware"),"."),(0,a.yg)("h2",{id:"integration"},"Integration"),(0,a.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,a.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,a.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,a.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"composer.json")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre"},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"index.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4919],{18328:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>t,metadata:()=>s,toc:()=>l});var n=i(58168),a=(i(96540),i(15680));i(67443);const t={id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},o=void 0,s={unversionedId:"universal_service_providers",id:"version-4.0/universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-4.0/universal_service_providers.md",sourceDirName:".",slug:"/universal_service_providers",permalink:"/docs/4.0/universal_service_providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/universal_service_providers.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},sidebar:"version-4.0/docs",previous:{title:"Laravel package",permalink:"/docs/4.0/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/4.0/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function h(e){let{components:r,...i}=e;return(0,a.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,a.yg)("p",null,"If your framework is compatible with ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,a.yg)("h2",{id:"installation"},"Installation"),(0,a.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,a.yg)("h2",{id:"requirements"},"Requirements"),(0,a.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,a.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,a.yg)("p",null,"GraphQLite relies on the ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,a.yg)("a",{parentName:"p",href:"/docs/4.0/other-frameworks"},"PSR-15 middleware"),"."),(0,a.yg)("h2",{id:"integration"},"Integration"),(0,a.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,a.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,a.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,a.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"composer.json")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre"},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"index.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/aa5b6080.673f0a63.js b/assets/js/aa5b6080.381bae1e.js similarity index 99% rename from assets/js/aa5b6080.673f0a63.js rename to assets/js/aa5b6080.381bae1e.js index 8b8cfae6fb..01cef11728 100644 --- a/assets/js/aa5b6080.673f0a63.js +++ b/assets/js/aa5b6080.381bae1e.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1894],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==o&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},35913:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"queries",title:"Queries",sidebar_label:"Queries",original_id:"queries"},s=void 0,u={unversionedId:"queries",id:"version-4.1/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-4.1/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/4.1/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/queries.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries",original_id:"queries"},sidebar:"version-4.1/docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/4.1/other-frameworks"},next:{title:"Mutations",permalink:"/docs/4.1/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...o}=e;return(0,r.yg)(h,(0,a.A)({},d,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1894],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function y(e){let{queryString:t=!1,groupId:n}=e;const a=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function g(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=y({queryString:n,groupId:a}),[c,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,p.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),m=(()=>{const e=s??c;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),g(e)}),[u,g,l]),tabValues:l}}var m=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=p.indexOf(t),a=u[n].value;a!==o&&(c(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;t=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;t=p[n]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>p.push(e),onKeyDown:h,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function T(e){const t=g(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(t)},e))}},35913:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"queries",title:"Queries",sidebar_label:"Queries",original_id:"queries"},s=void 0,u={unversionedId:"queries",id:"version-4.1/queries",title:"Queries",description:"In GraphQLite, GraphQL queries are created by writing methods in controller classes.",source:"@site/versioned_docs/version-4.1/queries.mdx",sourceDirName:".",slug:"/queries",permalink:"/docs/4.1/queries",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/queries.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"queries",title:"Queries",sidebar_label:"Queries",original_id:"queries"},sidebar:"version-4.1/docs",previous:{title:"Other frameworks / No framework",permalink:"/docs/4.1/other-frameworks"},next:{title:"Mutations",permalink:"/docs/4.1/mutations"}},p={},c=[{value:"Simple query",id:"simple-query",level:2},{value:"About annotations / attributes",id:"about-annotations--attributes",level:2},{value:"Testing the query",id:"testing-the-query",level:2},{value:"Query with a type",id:"query-with-a-type",level:2}],d={toc:c},h="wrapper";function y(e){let{components:t,...o}=e;return(0,r.yg)(h,(0,a.A)({},d,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, GraphQL queries are created by writing methods in ",(0,r.yg)("em",{parentName:"p"},"controller")," classes."),(0,r.yg)("p",null,"Those classes must be in the controllers namespaces which has been defined when you configured GraphQLite.\nFor instance, in Symfony, the controllers namespace is ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Controller")," by default."),(0,r.yg)("h2",{id:"simple-query"},"Simple query"),(0,r.yg)("p",null,"In a controller class, each query method must be annotated with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query")," annotation. For instance:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello ' . $name;\n }\n}\n")))),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Query {\n hello(name: String!): String!\n}\n")),(0,r.yg)("p",null,"As you can see, GraphQLite will automatically do the mapping between PHP types and GraphQL types."),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," If you are not using a framework with an autowiring container (like Symfony or Laravel), please be aware that the ",(0,r.yg)("code",null,"MyController")," class must exist in the container of your application. Furthermore, the identifier of the controller in the container MUST be the fully qualified class name of controller."),(0,r.yg)("h2",{id:"about-annotations--attributes"},"About annotations / attributes"),(0,r.yg)("p",null,"GraphQLite relies a lot on annotations (we call them attributes since PHP 8)."),(0,r.yg)("p",null,'It supports both the old "Doctrine annotations" style (',(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),") and the new PHP 8 attributes (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),")."),(0,r.yg)("p",null,"Read the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/doctrine-annotations-attributes"},"Doctrine annotations VS attributes")," documentation if you are not familiar with this concept."),(0,r.yg)("h2",{id:"testing-the-query"},"Testing the query"),(0,r.yg)("p",null,"The default GraphQL endpoint is ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql"),"."),(0,r.yg)("p",null,"The easiest way to test a GraphQL endpoint is to use ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/graphql/graphiql"},"GraphiQL")," or\n",(0,r.yg)("a",{parentName:"p",href:"https://altair.sirmuel.design/"},"Altair")," clients (they are available as Chrome or Firefox plugins)"),(0,r.yg)("div",{class:"alert alert--info"},"If you are using the Symfony bundle, GraphiQL is also directly embedded.",(0,r.yg)("br",null),"Simply head to ",(0,r.yg)("code",null,"http://[path-to-my-app]/graphiql")),(0,r.yg)("p",null,"Here a query using our simple ",(0,r.yg)("em",{parentName:"p"},"Hello World")," example:"),(0,r.yg)("p",null,(0,r.yg)("img",{src:n(67258).A,width:"1132",height:"352"})),(0,r.yg)("h2",{id:"query-with-a-type"},"Query with a type"),(0,r.yg)("p",null,"So far, we simply declared a query. But we did not yet declare a type."),(0,r.yg)("p",null,"Let's assume you want to return a product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass ProductController\n{\n /**\n * @Query\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"As the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is not a scalar type, you must tell GraphQLite how to handle it:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to inform GraphQLite that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class is a GraphQL type."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to define the GraphQL fields. This annotation must be put on a ",(0,r.yg)("strong",{parentName:"p"},"public method"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class must be in one of the ",(0,r.yg)("em",{parentName:"p"},"types")," namespaces. As for ",(0,r.yg)("em",{parentName:"p"},"controller")," classes, you configured this namespace when you installed\nGraphQLite. By default, in Symfony, the allowed types namespaces are ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Entity")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Types"),"."),(0,r.yg)("p",null,"This query is equivalent to the following ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Type Product {\n name: String!\n price: Float\n}\n")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("p",null,"If you are used to ",(0,r.yg)("a",{href:"https://en.wikipedia.org/wiki/Domain-driven_design"},"Domain driven design"),", you probably realize that the ",(0,r.yg)("code",null,"Product")," class is part of your ",(0,r.yg)("i",null,"domain"),"."),(0,r.yg)("p",null,"GraphQL annotations are adding some serialization logic that is out of scope of the domain. These are ",(0,r.yg)("i",null,"just")," annotations and for most project, this is the fastest and easiest route."),(0,r.yg)("p",null,"If you feel that GraphQL annotations do not belong to the domain, or if you cannot modify the class directly (maybe because it is part of a third party library), there is another way to create types without annotating the domain class. We will explore that in the next chapter.")))}y.isMDXComponent=!0},67258:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/query1-5a22bbe2398efcc725ea571a07ff2c9b.png"}}]); \ No newline at end of file diff --git a/assets/js/aa675676.dea071fb.js b/assets/js/aa675676.5d9c0e5d.js similarity index 99% rename from assets/js/aa675676.dea071fb.js rename to assets/js/aa675676.5d9c0e5d.js index 4ba130a079..4cf084e4d2 100644 --- a/assets/js/aa675676.dea071fb.js +++ b/assets/js/aa675676.5d9c0e5d.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2358],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>N});var n=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),d=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function c(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??p(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function m(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:t}=e;const n=(0,l.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function y(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!m({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=h({queryString:t,groupId:n}),[p,y]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,d.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),g=(()=>{const e=s??p;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,i]),tabValues:i}}var g=t(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:a,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),c=e=>{const a=e.currentTarget,t=d.indexOf(a),n=u[t].value;n!==l&&(p(a),s(n))},m=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const t=d.indexOf(e.currentTarget)+1;a=d[t]??d[0];break}case"ArrowLeft":{const t=d.indexOf(e.currentTarget)-1;a=d[t]??d[d.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===a?0:-1,"aria-selected":l===a,key:a,ref:e=>d.push(e),onKeyDown:m,onClick:c},o,{className:(0,i.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=y(e);return r.createElement("div",{className:(0,i.A)("tabs-container",v.tabList)},r.createElement(f,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function N(e){const a=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},52268:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation",original_id:"validation"},s=void 0,u={unversionedId:"validation",id:"version-4.1/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-4.1/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/4.1/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/validation.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation",original_id:"validation"},sidebar:"version-4.1/docs",previous:{title:"Error handling",permalink:"/docs/4.1/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/4.1/authentication_authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],c={toc:p},m="wrapper";function h(e){let{components:a,...t}=e;return(0,r.yg)(m,(0,n.A)({},c,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,r.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,r.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,r.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,r.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,r.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box"),(0,r.yg)("li",{parentName:"ul"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,r.yg)("em",{parentName:"li"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:",(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,r.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,r.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,r.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"UserController.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"UserController.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"User.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"User.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,r.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,r.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,r.yg)("p",null,"If the data entered by the user is ",(0,r.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,r.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\Graphqlite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,r.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,r.yg)("p",null,"You can also pass an array to the ",(0,r.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,r.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2358],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>N});var n=t(58168),r=t(96540),i=t(20053),o=t(23104),l=t(56347),s=t(57485),u=t(31682),d=t(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function c(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??p(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function m(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:t}=e;const n=(0,l.W6)(),i=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const a=new URLSearchParams(n.location.search);a.set(i,e),n.replace({...n.location,search:a.toString()})}),[i,n])]}function y(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,i=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!m({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:i}))),[s,u]=h({queryString:t,groupId:n}),[p,y]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,i]=(0,d.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:n}),g=(()=>{const e=s??p;return m({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,i]),tabValues:i}}var g=t(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:a,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),c=e=>{const a=e.currentTarget,t=d.indexOf(a),n=u[t].value;n!==l&&(p(a),s(n))},m=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const t=d.indexOf(e.currentTarget)+1;a=d[t]??d[0];break}case"ArrowLeft":{const t=d.indexOf(e.currentTarget)-1;a=d[t]??d[d.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===a?0:-1,"aria-selected":l===a,key:a,ref:e=>d.push(e),onKeyDown:m,onClick:c},o,{className:(0,i.A)("tabs__item",v.tabItem,o?.className,{"tabs__item--active":l===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=y(e);return r.createElement("div",{className:(0,i.A)("tabs-container",v.tabList)},r.createElement(f,(0,n.A)({},e,a)),r.createElement(b,(0,n.A)({},e,a)))}function N(e){const a=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(a)},e))}},52268:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=t(58168),r=(t(96540),t(15680)),i=(t(67443),t(11470)),o=t(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation",original_id:"validation"},s=void 0,u={unversionedId:"validation",id:"version-4.1/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-4.1/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/4.1/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/validation.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation",original_id:"validation"},sidebar:"version-4.1/docs",previous:{title:"Error handling",permalink:"/docs/4.1/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/4.1/authentication_authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3}],c={toc:p},m="wrapper";function h(e){let{components:a,...t}=e;return(0,r.yg)(m,(0,n.A)({},c,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,r.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,r.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,r.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.1/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,r.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,r.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box"),(0,r.yg)("li",{parentName:"ul"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,r.yg)("em",{parentName:"li"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:",(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,r.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,r.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,r.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"UserController.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"UserController.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\Graphqlite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"User.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"User.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,r.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,r.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,r.yg)("p",null,"If the data entered by the user is ",(0,r.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,r.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\Graphqlite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,r.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,r.yg)("p",null,"You can also pass an array to the ",(0,r.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,r.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/aba5bf07.b9f64b98.js b/assets/js/aba5bf07.da06d077.js similarity index 99% rename from assets/js/aba5bf07.b9f64b98.js rename to assets/js/aba5bf07.da06d077.js index 9188c5288d..f6a37c72da 100644 --- a/assets/js/aba5bf07.b9f64b98.js +++ b/assets/js/aba5bf07.da06d077.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3955],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),o=t(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),o=t(96540),i=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:o}}=e;return{value:n,label:t,attributes:a,default:o}}))}function d(e){const{values:n,children:t}=e;return(0,o.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,o.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,o.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,n)),o.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return o.createElement(v,(0,a.A)({key:String(n)},e))}},7168:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),o=(t(96540),t(15680)),i=(t(67443),t(11470)),r=t(19365);const l={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},u=void 0,s={unversionedId:"external-type-declaration",id:"version-7.0.0/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-7.0.0/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/external-type-declaration.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"docs",previous:{title:"Extending a type",permalink:"/docs/extend-type"},next:{title:"Input types",permalink:"/docs/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,o.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),").\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object.\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3955],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),o=t(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),o=t(96540),i=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:o}}=e;return{value:n,label:t,attributes:a,default:o}}))}function d(e){const{values:n,children:t}=e;return(0,o.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,o.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,o.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,n)),o.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return o.createElement(v,(0,a.A)({key:String(n)},e))}},7168:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),o=(t(96540),t(15680)),i=(t(67443),t(11470)),r=t(19365);const l={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},u=void 0,s={unversionedId:"external-type-declaration",id:"version-7.0.0/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-7.0.0/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/external-type-declaration.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"docs",previous:{title:"Extending a type",permalink:"/docs/extend-type"},next:{title:"Input types",permalink:"/docs/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,o.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),").\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object.\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ac8293fa.4b2a6b5f.js b/assets/js/ac8293fa.33c65881.js similarity index 88% rename from assets/js/ac8293fa.4b2a6b5f.js rename to assets/js/ac8293fa.33c65881.js index feb32fcc4f..097ccaffd2 100644 --- a/assets/js/ac8293fa.4b2a6b5f.js +++ b/assets/js/ac8293fa.33c65881.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[630],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=p(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),b=(()=>{const e=s??d;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==o&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,l.A)("tabs__item",y.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(f,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function T(e){const t=(0,b.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},89018:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},s=void 0,i={unversionedId:"index",id:"version-6.0/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-6.0/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/6.0/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/README.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"docs",next:{title:"Getting Started",permalink:"/docs/6.0/getting-started"}},c={},d=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],p={toc:d},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[630],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),b=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==o&&(p(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,l.A)("tabs__item",y.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(f,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function T(e){const t=(0,b.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},89018:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},s=void 0,i={unversionedId:"index",id:"version-6.0/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-6.0/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/6.0/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/README.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"docs",next:{title:"Getting Started",permalink:"/docs/6.0/getting-started"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:p},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/acbaac14.0e34867d.js b/assets/js/acbaac14.ce7e7bba.js similarity index 81% rename from assets/js/acbaac14.0e34867d.js rename to assets/js/acbaac14.ce7e7bba.js index 9cf1a7a0d8..58b548d559 100644 --- a/assets/js/acbaac14.0e34867d.js +++ b/assets/js/acbaac14.ce7e7bba.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8483],{19365:(e,a,t)=>{t.d(a,{A:()=>l});var n=t(96540),r=t(20053);const o={tabItem:"tabItem_Ymn6"};function l(e){let{children:a,hidden:t,className:l}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,l),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),o=t(20053),l=t(23104),i=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,i.W6)(),o=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const a=new URLSearchParams(n.location.search);a.set(o,e),n.replace({...n.location,search:a.toString()})}),[o,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,o=g(e),[l,i]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:o}))),[s,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,o]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:n}),m=(()=>{const e=s??c;return d({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,l.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==i&&(c(a),s(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:l}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===a?0:-1,"aria-selected":i===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},l,{className:(0,o.A)("tabs__item",f.tabItem,l?.className,{"tabs__item--active":i===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},66503:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),o=(t(67443),t(11470)),l=t(19365);const i={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},s=void 0,u={unversionedId:"fine-grained-security",id:"version-4.3/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-4.3/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/4.3/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/fine-grained-security.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},sidebar:"version-4.3/docs",previous:{title:"Authentication and authorization",permalink:"/docs/4.3/authentication-authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/4.3/implementing-security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/authentication-authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/authentication-authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8483],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),l=t(20053),o=t(23104),i=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,i.W6)(),l=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(n.location.search);a.set(l,e),n.replace({...n.location,search:a.toString()})}),[l,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,l=g(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:l}))),[s,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,l]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:n}),m=(()=>{const e=s??c;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==i&&(c(a),s(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===a?0:-1,"aria-selected":i===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},66503:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),o=t(19365);const i={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},s=void 0,u={unversionedId:"fine-grained-security",id:"version-4.3/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-4.3/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/4.3/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/fine-grained-security.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},sidebar:"version-4.3/docs",previous:{title:"Authentication and authorization",permalink:"/docs/4.3/authentication-authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/4.3/implementing-security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/authentication-authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/authentication-authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ae0a12ed.e990137c.js b/assets/js/ae0a12ed.108279d2.js similarity index 81% rename from assets/js/ae0a12ed.e990137c.js rename to assets/js/ae0a12ed.108279d2.js index 87b1e12f99..2e1b4613ec 100644 --- a/assets/js/ae0a12ed.e990137c.js +++ b/assets/js/ae0a12ed.108279d2.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2998],{19365:(e,a,t)=>{t.d(a,{A:()=>l});var n=t(96540),r=t(20053);const o={tabItem:"tabItem_Ymn6"};function l(e){let{children:a,hidden:t,className:l}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,l),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),o=t(20053),l=t(23104),i=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,i.W6)(),o=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const a=new URLSearchParams(n.location.search);a.set(o,e),n.replace({...n.location,search:a.toString()})}),[o,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,o=g(e),[l,i]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:o}))),[s,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,o]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:n}),m=(()=>{const e=s??c;return d({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:l,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,l.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==i&&(c(a),s(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:l}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===a?0:-1,"aria-selected":i===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},l,{className:(0,o.A)("tabs__item",f.tabItem,l?.className,{"tabs__item--active":i===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},91830:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),o=(t(67443),t(11470)),l=t(19365);const i={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},s=void 0,u={unversionedId:"fine-grained-security",id:"version-4.2/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-4.2/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/4.2/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/fine-grained-security.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},sidebar:"version-4.2/docs",previous:{title:"Authentication and authorization",permalink:"/docs/4.2/authentication-authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/4.2/implementing-security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/authentication-authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/authentication-authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(l.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(l.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2998],{19365:(e,a,t)=>{t.d(a,{A:()=>o});var n=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:a,hidden:t,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>T});var n=t(58168),r=t(96540),l=t(20053),o=t(23104),i=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:r}}=e;return{value:a,label:t,attributes:n,default:r}}))}function g(e){const{values:a,children:t}=e;return(0,r.useMemo)((()=>{const e=a??c(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function d(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function y(e){let{queryString:a=!1,groupId:t}=e;const n=(0,i.W6)(),l=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(n.location.search);a.set(l,e),n.replace({...n.location,search:a.toString()})}),[l,n])]}function h(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,l=g(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!d({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:l}))),[s,u]=y({queryString:t,groupId:n}),[c,h]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,l]=(0,p.Dv)(t);return[n,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:n}),m=(()=>{const e=s??c;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&i(m)}),[m]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:a,block:t,selectedValue:i,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),g=e=>{const a=e.currentTarget,t=p.indexOf(a),n=u[t].value;n!==i&&(c(a),s(n))},d=e=>{let a=null;switch(e.key){case"Enter":g(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;a=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;a=p[t]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===a?0:-1,"aria-selected":i===a,key:a,ref:e=>p.push(e),onKeyDown:d,onClick:g},o,{className:(0,l.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":i===a})}),t??a)})))}function v(e){let{lazy:a,children:t,selectedValue:n}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function N(e){const a=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,a)),r.createElement(v,(0,n.A)({},e,a)))}function T(e){const a=(0,m.A)();return r.createElement(N,(0,n.A)({key:String(a)},e))}},91830:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>y,frontMatter:()=>i,metadata:()=>u,toc:()=>c});var n=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),o=t(19365);const i={id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},s=void 0,u={unversionedId:"fine-grained-security",id:"version-4.2/fine-grained-security",title:"Fine grained security",description:"If the @Logged and @Right annotations are not",source:"@site/versioned_docs/version-4.2/fine-grained-security.mdx",sourceDirName:".",slug:"/fine-grained-security",permalink:"/docs/4.2/fine-grained-security",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/fine-grained-security.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"fine-grained-security",title:"Fine grained security",sidebar_label:"Fine grained security"},sidebar:"version-4.2/docs",previous:{title:"Authentication and authorization",permalink:"/docs/4.2/authentication-authorization"},next:{title:"Connecting security to your framework",permalink:"/docs/4.2/implementing-security"}},p={},c=[{value:"Using the @Security annotation",id:"using-the-security-annotation",level:2},{value:"The is_granted function",id:"the-is_granted-function",level:2},{value:"Accessing method parameters",id:"accessing-method-parameters",level:2},{value:"Setting HTTP code and error message",id:"setting-http-code-and-error-message",level:2},{value:"Setting a default value",id:"setting-a-default-value",level:2},{value:"Accessing the user",id:"accessing-the-user",level:2},{value:"Accessing the current object",id:"accessing-the-current-object",level:2},{value:"Available scope",id:"available-scope",level:2},{value:"How to restrict access to a given resource",id:"how-to-restrict-access-to-a-given-resource",level:2}],g={toc:c},d="wrapper";function y(e){let{components:a,...t}=e;return(0,r.yg)(d,(0,n.A)({},g,t,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"If the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/authentication-authorization#logged-and-right-annotations"},(0,r.yg)("inlineCode",{parentName:"a"},"@Logged")," and ",(0,r.yg)("inlineCode",{parentName:"a"},"@Right")," annotations")," are not\ngranular enough for your needs, you can use the advanced ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"Using the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation, you can write an ",(0,r.yg)("em",{parentName:"p"},"expression")," that can contain custom logic. For instance:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Check that a user can access a given resource"),(0,r.yg)("li",{parentName:"ul"},"Check that a user has one right or another right"),(0,r.yg)("li",{parentName:"ul"},"...")),(0,r.yg)("h2",{id:"using-the-security-annotation"},"Using the @Security annotation"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation is very flexible: it allows you to pass an expression that can contains custom logic:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n#[Query]\n#[Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Security;\n\n// ...\n\n/**\n * @Query\n * @Security(\"is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("em",{parentName:"p"},"expression")," defined in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation must conform to ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/4.4/components/expression_language/syntax.html"},"Symfony's Expression Language syntax")),(0,r.yg)("div",{class:"alert alert--info"},"If you are a Symfony user, you might already be used to the ",(0,r.yg)("code",null,"@Security")," annotation. Most of the inspiration of this annotation comes from Symfony. Warning though! GraphQLite's ",(0,r.yg)("code",null,"@Security")," annotation and Symfony's ",(0,r.yg)("code",null,"@Security")," annotation are slightly different. Especially, the two annotations do not live in the same namespace!"),(0,r.yg)("h2",{id:"the-is_granted-function"},"The ",(0,r.yg)("inlineCode",{parentName:"h2"},"is_granted")," function"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function to check if a user has a special right."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('ROLE_ADMIN')\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('ROLE_ADMIN')\")\n")))),(0,r.yg)("p",null,"is similar to"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Right("ROLE_ADMIN")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'@Right("ROLE_ADMIN")\n')))),(0,r.yg)("p",null,"In addition, the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted"),' function accepts a second optional parameter: the "scope" of the right.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(\"is_granted('POST_SHOW', post)\")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @Security(\"is_granted('POST_SHOW', post)\")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"In the example above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"getPost")," method can be called only if the logged user has the 'POST_SHOW' permission on the\n",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object. You can notice that the ",(0,r.yg)("inlineCode",{parentName:"p"},"$post")," object comes from the parameters."),(0,r.yg)("h2",{id:"accessing-method-parameters"},"Accessing method parameters"),(0,r.yg)("p",null,"All parameters passed to the method can be accessed in the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," expression."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "startDate < endDate", statusCode: 400, message: "End date must be after start date")]\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("startDate < endDate", statusCode=400, message="End date must be after start date")\n */\npublic function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"In the example above, we tweak a bit the Security annotation purpose to do simple input validation."),(0,r.yg)("h2",{id:"setting-http-code-and-error-message"},"Setting HTTP code and error message"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes to set the HTTP code and GraphQL error message."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security(expression: "is_granted(\'POST_SHOW\', post)", statusCode: 404, message: "Post not found (let\'s pretend the post does not exists!)")]\npublic function getPost(Post $post): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_granted(\'POST_SHOW\', post)", statusCode=404, message="Post not found (let\'s pretend the post does not exists!)")\n */\npublic function getPost(Post $post): array\n{\n // ...\n}\n')))),(0,r.yg)("p",null,"Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.\nThe resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the\nhigher error code will be returned."),(0,r.yg)("h2",{id:"setting-a-default-value"},"Setting a default value"),(0,r.yg)("p",null,"If you do not want an error to be thrown when the security condition is not met, you can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute\nto set a default value."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Query]\n#[Security(expression: \"is_granted('CAN_SEE_MARGIN', this)\", failWith: null)]\npublic function getMargin(): float\n{\n // ...\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field\n * @Security(\"is_granted('CAN_SEE_MARGIN', this)\", failWith=null)\n */\npublic function getMargin(): float\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute behaves just like the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/authentication-authorization#not-throwing-errors"},(0,r.yg)("inlineCode",{parentName:"a"},"@FailWith")," annotation"),"\nbut for a given ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation."),(0,r.yg)("p",null,"You cannot use the ",(0,r.yg)("inlineCode",{parentName:"p"},"failWith")," attribute along ",(0,r.yg)("inlineCode",{parentName:"p"},"statusCode")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"message")," attributes."),(0,r.yg)("h2",{id:"accessing-the-user"},"Accessing the user"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," variable to access the currently logged user.\nYou can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_logged()")," function to check if a user is logged or not."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Query]\n#[Security("is_logged() && user.age > 18")]\npublic function getNSFWImages(): array\n{\n // ...\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Query\n * @Security("is_logged() && user.age > 18")\n */\npublic function getNSFWImages(): array\n{\n // ...\n}\n')))),(0,r.yg)("h2",{id:"accessing-the-current-object"},"Accessing the current object"),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"this")," variable to access any (public) property / method of the current class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n #[Field]\n #[Security("this.canAccessBody(user)")]\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class Post {\n /**\n * @Field\n * @Security("this.canAccessBody(user)")\n */\n public function getBody(): array\n {\n // ...\n }\n\n public function canAccessBody(User $user): bool\n {\n // Some custom logic here\n }\n}\n')))),(0,r.yg)("h2",{id:"available-scope"},"Available scope"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used in any query, mutation or field, so anywhere you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation"),"\nor ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,r.yg)("h2",{id:"how-to-restrict-access-to-a-given-resource"},"How to restrict access to a given resource"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," method can be used to restrict access to a specific resource."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Security(\"is_granted('POST_SHOW', post)\")]\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"@Security(\"is_granted('POST_SHOW', post)\")\n")))),(0,r.yg)("p",null,"If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles\nitself. Instead, this depends on the framework you are using."),(0,r.yg)("p",null,"If you are using Symfony, you will ",(0,r.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/security/voters.html"},"create a custom voter"),"."),(0,r.yg)("p",null,"If you are using Laravel, you will ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/authorization"},"create a Gate or a Policy"),"."),(0,r.yg)("p",null,"If you are using another framework, you need to know that the ",(0,r.yg)("inlineCode",{parentName:"p"},"is_granted")," function simply forwards the call to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"isAllowed")," method of the configured ",(0,r.yg)("inlineCode",{parentName:"p"},"AuthorizationSerice"),". See ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/implementing-security"},"Connecting GraphQLite to your framework's security module\n")," for more details"))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/aebf35b6.8952f146.js b/assets/js/aebf35b6.7c35d9f1.js similarity index 95% rename from assets/js/aebf35b6.8952f146.js rename to assets/js/aebf35b6.7c35d9f1.js index cbe347d59d..b7c5a39285 100644 --- a/assets/js/aebf35b6.8952f146.js +++ b/assets/js/aebf35b6.7c35d9f1.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8357],{53682:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>p,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var a=n(58168),i=(n(96540),n(15680));n(67443);const l={id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types",original_id:"multiple_output_types"},p=void 0,o={unversionedId:"multiple_output_types",id:"version-4.0/multiple_output_types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.0/multiple_output_types.mdx",sourceDirName:".",slug:"/multiple_output_types",permalink:"/docs/4.0/multiple_output_types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/multiple_output_types.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types",original_id:"multiple_output_types"},sidebar:"version-4.0/docs",previous:{title:"Extending an input type",permalink:"/docs/4.0/extend_input_type"},next:{title:"Symfony specific features",permalink:"/docs/4.0/symfony-bundle-advanced"}},u={},s=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],d={toc:s},r="wrapper";function c(e){let{components:t,...n}=e;return(0,i.yg)(r,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("small",null,"Available in GraphQLite 4.0+"),(0,i.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,i.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,i.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,i.yg)("h2",{id:"example"},"Example"),(0,i.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(Product $product): string\n {\n return $product->getName();\n }\n}\n')),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares a ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/external_type_declaration"},'"external" type')," mapping the ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,i.yg)("p",null,"First of all, we specify ",(0,i.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,i.yg)("p",null,"Then, we specify ",(0,i.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,i.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,i.yg)("p",null,"Finally, we can write our requests:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')),(0,i.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,i.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,i.yg)("p",null,"Is a result, when the end user calls the ",(0,i.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,i.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,i.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,i.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,i.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,i.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,i.yg)("p",null,"If you want to extend a type using the ",(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,i.yg)("p",null,"So instead of writing:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")),(0,i.yg)("p",null,"you will write:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')),(0,i.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8357],{53682:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>p,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var a=n(58168),i=(n(96540),n(15680));n(67443);const l={id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types",original_id:"multiple_output_types"},p=void 0,o={unversionedId:"multiple_output_types",id:"version-4.0/multiple_output_types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.0/multiple_output_types.mdx",sourceDirName:".",slug:"/multiple_output_types",permalink:"/docs/4.0/multiple_output_types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/multiple_output_types.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"multiple_output_types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types",original_id:"multiple_output_types"},sidebar:"version-4.0/docs",previous:{title:"Extending an input type",permalink:"/docs/4.0/extend_input_type"},next:{title:"Symfony specific features",permalink:"/docs/4.0/symfony-bundle-advanced"}},u={},s=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],r={toc:s},d="wrapper";function c(e){let{components:t,...n}=e;return(0,i.yg)(d,(0,a.A)({},r,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("small",null,"Available in GraphQLite 4.0+"),(0,i.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,i.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,i.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,i.yg)("h2",{id:"example"},"Example"),(0,i.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(Product $product): string\n {\n return $product->getName();\n }\n}\n')),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares a ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/external_type_declaration"},'"external" type')," mapping the ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,i.yg)("p",null,"First of all, we specify ",(0,i.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,i.yg)("p",null,"Then, we specify ",(0,i.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,i.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,i.yg)("p",null,"Finally, we can write our requests:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')),(0,i.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,i.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,i.yg)("p",null,"Is a result, when the end user calls the ",(0,i.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,i.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,i.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,i.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,i.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,i.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,i.yg)("p",null,"If you want to extend a type using the ",(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,i.yg)("p",null,"So instead of writing:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")),(0,i.yg)("p",null,"you will write:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')),(0,i.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,i.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b06c439f.ed04ab32.js b/assets/js/b06c439f.1814399b.js similarity index 98% rename from assets/js/b06c439f.ed04ab32.js rename to assets/js/b06c439f.1814399b.js index 5fe1dd438e..b18ff97098 100644 --- a/assets/js/b06c439f.ed04ab32.js +++ b/assets/js/b06c439f.1814399b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7906],{99687:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,o={unversionedId:"internals",id:"version-4.3/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-4.3/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/4.3/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/internals.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"version-4.3/docs",previous:{title:"Laravel specific features",permalink:"/docs/4.3/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/4.3/troubleshooting"}},l={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.3/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7906],{99687:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,o={unversionedId:"internals",id:"version-4.3/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-4.3/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/4.3/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/internals.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"version-4.3/docs",previous:{title:"Laravel specific features",permalink:"/docs/4.3/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/4.3/troubleshooting"}},l={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.3/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b0ed7ea5.73400342.js b/assets/js/b0ed7ea5.59abd713.js similarity index 56% rename from assets/js/b0ed7ea5.73400342.js rename to assets/js/b0ed7ea5.59abd713.js index c19c9344d3..5276b3e046 100644 --- a/assets/js/b0ed7ea5.73400342.js +++ b/assets/js/b0ed7ea5.59abd713.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7940],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),o=t(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),o=t(96540),i=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:o}}=e;return{value:n,label:t,attributes:a,default:o}}))}function d(e){const{values:n,children:t}=e;return(0,o.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,o.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,o.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,n)),o.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return o.createElement(v,(0,a.A)({key:String(n)},e))}},12940:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),o=(t(96540),t(15680)),i=(t(67443),t(11470)),r=t(19365);const l={id:"external_type_declaration",title:"External type declaration",sidebar_label:"External type declaration",original_id:"external_type_declaration"},u=void 0,s={unversionedId:"external_type_declaration",id:"version-4.1/external_type_declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-4.1/external_type_declaration.mdx",sourceDirName:".",slug:"/external_type_declaration",permalink:"/docs/4.1/external_type_declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/external_type_declaration.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"external_type_declaration",title:"External type declaration",sidebar_label:"External type declaration",original_id:"external_type_declaration"},sidebar:"version-4.1/docs",previous:{title:"Extending a type",permalink:"/docs/4.1/extend_type"},next:{title:"Input types",permalink:"/docs/4.1/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,o.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),")."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/4.1/authentication_authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/4.1/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7940],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),i=t(20053);const o={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,i.A)(o.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),i=t(96540),o=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return i.Children.map(e,(e=>{if(!e||(0,i.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:i}}=e;return{value:n,label:t,attributes:a,default:i}}))}function d(e){const{values:n,children:t}=e;return(0,i.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(o),(0,i.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(a.location.search);n.set(o,e),a.replace({...a.location,search:n.toString()})}),[o,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,o=d(e),[r,l]=(0,i.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:o}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,o]=(0,c.Dv)(t);return[a,(0,i.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:o})?e:null})();(0,i.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,i.useCallback)((e=>{if(!y({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return i.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,o.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===a));return e?(0,i.cloneElement)(e,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,i.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return i.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},i.createElement(b,(0,a.A)({},e,n)),i.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return i.createElement(v,(0,a.A)({key:String(n)},e))}},12940:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),i=(t(96540),t(15680)),o=(t(67443),t(11470)),r=t(19365);const l={id:"external_type_declaration",title:"External type declaration",sidebar_label:"External type declaration",original_id:"external_type_declaration"},u=void 0,s={unversionedId:"external_type_declaration",id:"version-4.1/external_type_declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-4.1/external_type_declaration.mdx",sourceDirName:".",slug:"/external_type_declaration",permalink:"/docs/4.1/external_type_declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/external_type_declaration.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"external_type_declaration",title:"External type declaration",sidebar_label:"External type declaration",original_id:"external_type_declaration"},sidebar:"version-4.1/docs",previous:{title:"Extending a type",permalink:"/docs/4.1/extend_type"},next:{title:"Input types",permalink:"/docs/4.1/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,i.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,i.yg)("p",null,"For instance:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,i.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,i.yg)("li",{parentName:"ul"},"etc.")),(0,i.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,i.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,i.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,i.yg)("p",null,"GraphQLite allows you to use a ",(0,i.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,i.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,i.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,i.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!")," The ",(0,i.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,i.yg)("br",null),(0,i.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,i.yg)("p",null,"In methods with a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,i.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,i.yg)("h2",{id:"sourcefield-annotation"},(0,i.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,i.yg)("p",null,"If you don't want to rewrite all ",(0,i.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,i.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,i.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,i.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,i.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,i.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,i.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"isName()"),")."),(0,i.yg)("h2",{id:"magicfield-annotation"},(0,i.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,i.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,i.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,i.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,i.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,i.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,i.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,i.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,i.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,i.yg)("p",null,"Any annotations described in the ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.1/authentication_authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.1/field-middlewares"},'"field middleware"')," can be used in the ",(0,i.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,i.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,i.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,i.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,i.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,i.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b103c05a.66f0d4d1.js b/assets/js/b103c05a.2c121ae6.js similarity index 99% rename from assets/js/b103c05a.66f0d4d1.js rename to assets/js/b103c05a.2c121ae6.js index d4b7f76e50..5f10dff9b9 100644 --- a/assets/js/b103c05a.66f0d4d1.js +++ b/assets/js/b103c05a.2c121ae6.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3672],{10724:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>g,contentTitle:()=>i,default:()=>m,frontMatter:()=>l,metadata:()=>p,toc:()=>y});var n=a(58168),r=(a(96540),a(15680));a(67443);const l={id:"annotations-reference",title:"Attributes reference",sidebar_label:"Attributes reference"},i=void 0,p={unversionedId:"annotations-reference",id:"annotations-reference",title:"Attributes reference",description:"Note: all annotations are available in PHP 8 attribute format (#[Query]), support of Doctrine annotation format was dropped.",source:"@site/docs/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/next/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/annotations-reference.md",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"annotations-reference",title:"Attributes reference",sidebar_label:"Attributes reference"},sidebar:"docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/next/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/next/semver"}},g={},y=[{value:"#Query",id:"query",level:2},{value:"#Mutation",id:"mutation",level:2},{value:"#Subscription",id:"subscription",level:2},{value:"#Type",id:"type",level:2},{value:"#ExtendType",id:"extendtype",level:2},{value:"#Input",id:"input",level:2},{value:"#Field",id:"field",level:2},{value:"#SourceField",id:"sourcefield",level:2},{value:"#MagicField",id:"magicfield",level:2},{value:"#Prefetch",id:"prefetch",level:2},{value:"#Logged",id:"logged",level:2},{value:"#Right",id:"right",level:2},{value:"#FailWith",id:"failwith",level:2},{value:"#HideIfUnauthorized",id:"hideifunauthorized",level:2},{value:"#InjectUser",id:"injectuser",level:2},{value:"#Security",id:"security",level:2},{value:"#Factory",id:"factory",level:2},{value:"#UseInputType",id:"useinputtype",level:2},{value:"#Decorate",id:"decorate",level:2},{value:"#Autowire",id:"autowire",level:2},{value:"#HideParameter",id:"hideparameter",level:2},{value:"#Cost",id:"cost",level:2},{value:"#Validate",id:"validate",level:2},{value:"#Assertion",id:"assertion",level:2},{value:"@EnumType",id:"enumtype",level:2}],o={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},o,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Note: all annotations are available in PHP 8 attribute format (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),"), support of Doctrine annotation format was dropped.\nSee ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,r.yg)("h2",{id:"query"},"#","[Query]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]")," attribute is used to declare a GraphQL query."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,r.yg)("h2",{id:"mutation"},"#","[Mutation]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," attribute is used to declare a GraphQL mutation."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,r.yg)("h2",{id:"subscription"},"#","[Subscription]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Subscription]")," attribute is used to declare a GraphQL subscription."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the subscription. If skipped, the name of the method is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Defines the GraphQL output type that will be sent for the subscription.")))),(0,r.yg)("h2",{id:"type"},"#","[Type]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute is used to declare a GraphQL object type. This is used with standard output\ntypes, as well as enum types. For input types, use the ",(0,r.yg)("a",{parentName:"p",href:"#input-annotation"},"#[Input] attribute")," directly on the input type or a ",(0,r.yg)("a",{parentName:"p",href:"#factory-annotation"},"#[Factory] attribute")," to make/return an input type."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"class"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},'The targeted class/enum for the actual type. If no "class" attribute is passed, the type applies to the current class/enum. The current class/enum is assumed to be an entity (not service). If the "class" attribute ',(0,r.yg)("em",{parentName:"td"},"is passed"),", ",(0,r.yg)("a",{parentName:"td",href:"/docs/next/external-type-declaration"},"the class/enum annotated with ",(0,r.yg)("inlineCode",{parentName:"a"},"#[Type]")," becomes a service"),".")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"default"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"bool"),(0,r.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,r.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"external"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"bool"),(0,r.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,r.yg)("a",{parentName:"td",href:"/docs/next/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,r.yg)("h2",{id:"extendtype"},"#","[ExtendType]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," attribute is used to add fields to an existing GraphQL object type."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"class"),(0,r.yg)("td",{parentName:"tr",align:null},"see below"),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,r.yg)("a",{parentName:"td",href:"/docs/next/extend-type"},"The class annotated with ",(0,r.yg)("inlineCode",{parentName:"a"},"#[ExtendType]")," is a service"),".")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},"see below"),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,r.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,r.yg)("h2",{id:"input"},"#","[Input]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute is used to declare a GraphQL input type."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"description"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"default"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"bool"),(0,r.yg)("td",{parentName:"tr",align:null},"Name of the input type represented in your GraphQL schema. Defaults to ",(0,r.yg)("inlineCode",{parentName:"td"},"true")," ",(0,r.yg)("em",{parentName:"td"},"only if")," the name is not specified. If ",(0,r.yg)("inlineCode",{parentName:"td"},"name")," is specified, this will default to ",(0,r.yg)("inlineCode",{parentName:"td"},"false"),", so must also be included for ",(0,r.yg)("inlineCode",{parentName:"td"},"true")," when ",(0,r.yg)("inlineCode",{parentName:"td"},"name")," is used.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"update"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"bool"),(0,r.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,r.yg)("inlineCode",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation/subscription. This primarily applies to nullable fields.")))),(0,r.yg)("h2",{id:"field"},"#","[Field]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute is used to declare a GraphQL field."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Type]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Input]"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,r.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,r.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,r.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"for"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string, array"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"description"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/input-types"},"inputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,r.yg)("h2",{id:"sourcefield"},"#","[SourceField]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[SourceField]")," attribute is used to declare a GraphQL field."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Type]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[ExtendType]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"phpType"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"description"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,r.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"annotations"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"array\\"),(0,r.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "#',"[Logged]",'" or "#',"[Right]",'" attribute as class here.')))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Note"),": ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,r.yg)("h2",{id:"magicfield"},"#","[MagicField]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[MagicField]")," attribute is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,r.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Type]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[ExtendType]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no"),"(*)"),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"phpType"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no"),"(*)"),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"description"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If not set, no description will be shown.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,r.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"annotations"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"array\\"),(0,r.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "#',"[Logged]",'" or "#',"[Right]",'" attribute as class here.')))),(0,r.yg)("p",null,"(*) ",(0,r.yg)("strong",{parentName:"p"},"Note"),": ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,r.yg)("h2",{id:"prefetch"},"#","[Prefetch]"),(0,r.yg)("p",null,"Marks field parameter to be used for ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/prefetch-method"},"prefetching"),"."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": parameters of methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"callable"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"callable"),(0,r.yg)("td",{parentName:"tr",align:null},"Name of the prefetch method (in same class) or a full callable, either a static method or regular service from the container")))),(0,r.yg)("h2",{id:"logged"},"#","[Logged]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Logged]")," attribute is used to declare a Query/Mutation/Field is only visible to logged users."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),"."),(0,r.yg)("p",null,"This attribute allows no arguments."),(0,r.yg)("h2",{id:"right"},"#","[Right]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Right]")," attribute is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,r.yg)("h2",{id:"failwith"},"#","[FailWith]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[FailWith]")," attribute is used to declare a default value to return in the user is not authorized to see a specific\nquery/mutation/subscription/field (according to the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Logged]")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Right]")," attributes)."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," and one of ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Logged]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Right]")," attributes."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"value"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"mixed"),(0,r.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,r.yg)("h2",{id:"hideifunauthorized"},"#","[HideIfUnauthorized]"),(0,r.yg)("div",{class:"alert alert--warning"},"This attribute only works when a Schema is used to handle exactly one use request. If you serve your GraphQL API from long-running standalone servers (like Laravel Octane, Swoole, RoadRunner etc) and share the same Schema instance between multiple requests, please avoid using #[HideIfUnauthorized]."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[HideIfUnauthorized]")," attribute is used to completely hide the query/mutation/subscription/field if the user is not authorized\nto access it (according to the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Logged]")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Right]")," attributes)."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," and one of ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Logged]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Right]")," attributes."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"#[HideIfUnauthorized]")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"#[FailWith]")," are mutually exclusive."),(0,r.yg)("h2",{id:"injectuser"},"#","[InjectUser]"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[InjectUser]")," attribute to inject an instance of the current user logged in into a parameter of your\nquery/mutation/subscription/field."),(0,r.yg)("p",null,"See ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/authentication-authorization"},"the authentication and authorization page")," for more details."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,r.yg)("h2",{id:"security"},"#","[Security]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,r.yg)("p",null,"See ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/fine-grained-security"},"the fine grained security page")," for more details."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"default")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,r.yg)("h2",{id:"factory"},"#","[Factory]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Factory]")," attribute is used to declare a factory that turns GraphQL input types into objects."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"default"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"bool"),(0,r.yg)("td",{parentName:"tr",align:null},"If ",(0,r.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,r.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,r.yg)("a",{parentName:"td",href:"/docs/next/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,r.yg)("inlineCode",{parentName:"a"},"#[Parameter]")," attribute"),".")))),(0,r.yg)("h2",{id:"useinputtype"},"#","[UseInputType]"),(0,r.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"inputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,r.yg)("h2",{id:"decorate"},"#","[Decorate]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Decorate]")," attribute is used ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,r.yg)("inlineCode",{parentName:"a"},"#[Factory]")," attribute"),"."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,r.yg)("h2",{id:"autowire"},"#","[Autowire]"),(0,r.yg)("p",null,(0,r.yg)("a",{parentName:"p",href:"/docs/next/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,r.yg)("p",null,"Useful to inject services directly into ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," method arguments."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"identifier")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,r.yg)("h2",{id:"hideparameter"},"#","[HideParameter]"),(0,r.yg)("p",null,"Removes ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,r.yg)("h2",{id:"cost"},"#","[Cost]"),(0,r.yg)("p",null,"Sets complexity and multipliers on fields for ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/operation-complexity#static-request-analysis"},"automatic query complexity"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"complexity")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"int"),(0,r.yg)("td",{parentName:"tr",align:null},"Complexity for that field")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"multipliers")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"array\\"),(0,r.yg)("td",{parentName:"tr",align:null},"Names of fields by value of which complexity will be multiplied")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"defaultMultiplier")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"int"),(0,r.yg)("td",{parentName:"tr",align:null},"Default multiplier value if all multipliers are missing/null")))),(0,r.yg)("h2",{id:"validate"},"#","[Validate]"),(0,r.yg)("div",{class:"alert alert--info"},"This attribute is only available in the GraphQLite Laravel package"),(0,r.yg)("p",null,(0,r.yg)("a",{parentName:"p",href:"/docs/next/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Factory]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Decorator]")," attribute."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"rule")),(0,r.yg)("td",{parentName:"tr",align:null},"*yes"),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,r.yg)("p",null,"Sample:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Validate(for: "$email", rule: "email|unique:users")]\n')),(0,r.yg)("h2",{id:"assertion"},"#","[Assertion]"),(0,r.yg)("p",null,(0,r.yg)("a",{parentName:"p",href:"/docs/next/validation"},"Validates a user input"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Assertion]")," attribute is available in the ",(0,r.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Factory]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Decorator]")," attribute."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"constraint")),(0,r.yg)("td",{parentName:"tr",align:null},"*yes"),(0,r.yg)("td",{parentName:"tr",align:null},"annotation"),(0,r.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation attributes.")))),(0,r.yg)("h2",{id:"enumtype"},(0,r.yg)("del",{parentName:"h2"},"@EnumType")),(0,r.yg)("p",null,(0,r.yg)("em",{parentName:"p"},"Deprecated: Use ",(0,r.yg)("a",{parentName:"em",href:"https://www.php.net/manual/en/language.types.enumerations.php"},"PHP 8.1's native Enums")," instead with a ",(0,r.yg)("a",{parentName:"em",href:"#type-annotation"},"#[Type]"),".")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,r.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,r.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3672],{10724:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>g,contentTitle:()=>i,default:()=>m,frontMatter:()=>l,metadata:()=>p,toc:()=>y});var n=a(58168),r=(a(96540),a(15680));a(67443);const l={id:"annotations-reference",title:"Attributes reference",sidebar_label:"Attributes reference"},i=void 0,p={unversionedId:"annotations-reference",id:"annotations-reference",title:"Attributes reference",description:"Note: all annotations are available in PHP 8 attribute format (#[Query]), support of Doctrine annotation format was dropped.",source:"@site/docs/annotations-reference.md",sourceDirName:".",slug:"/annotations-reference",permalink:"/docs/next/annotations-reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/annotations-reference.md",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"annotations-reference",title:"Attributes reference",sidebar_label:"Attributes reference"},sidebar:"docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/next/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/next/semver"}},g={},y=[{value:"#Query",id:"query",level:2},{value:"#Mutation",id:"mutation",level:2},{value:"#Subscription",id:"subscription",level:2},{value:"#Type",id:"type",level:2},{value:"#ExtendType",id:"extendtype",level:2},{value:"#Input",id:"input",level:2},{value:"#Field",id:"field",level:2},{value:"#SourceField",id:"sourcefield",level:2},{value:"#MagicField",id:"magicfield",level:2},{value:"#Prefetch",id:"prefetch",level:2},{value:"#Logged",id:"logged",level:2},{value:"#Right",id:"right",level:2},{value:"#FailWith",id:"failwith",level:2},{value:"#HideIfUnauthorized",id:"hideifunauthorized",level:2},{value:"#InjectUser",id:"injectuser",level:2},{value:"#Security",id:"security",level:2},{value:"#Factory",id:"factory",level:2},{value:"#UseInputType",id:"useinputtype",level:2},{value:"#Decorate",id:"decorate",level:2},{value:"#Autowire",id:"autowire",level:2},{value:"#HideParameter",id:"hideparameter",level:2},{value:"#Cost",id:"cost",level:2},{value:"#Validate",id:"validate",level:2},{value:"#Assertion",id:"assertion",level:2},{value:"@EnumType",id:"enumtype",level:2}],o={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},o,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Note: all annotations are available in PHP 8 attribute format (",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),"), support of Doctrine annotation format was dropped.\nSee ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,r.yg)("h2",{id:"query"},"#","[Query]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]")," attribute is used to declare a GraphQL query."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,r.yg)("h2",{id:"mutation"},"#","[Mutation]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," attribute is used to declare a GraphQL mutation."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,r.yg)("h2",{id:"subscription"},"#","[Subscription]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Subscription]")," attribute is used to declare a GraphQL subscription."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the subscription. If skipped, the name of the method is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Defines the GraphQL output type that will be sent for the subscription.")))),(0,r.yg)("h2",{id:"type"},"#","[Type]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute is used to declare a GraphQL object type. This is used with standard output\ntypes, as well as enum types. For input types, use the ",(0,r.yg)("a",{parentName:"p",href:"#input-annotation"},"#[Input] attribute")," directly on the input type or a ",(0,r.yg)("a",{parentName:"p",href:"#factory-annotation"},"#[Factory] attribute")," to make/return an input type."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"class"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},'The targeted class/enum for the actual type. If no "class" attribute is passed, the type applies to the current class/enum. The current class/enum is assumed to be an entity (not service). If the "class" attribute ',(0,r.yg)("em",{parentName:"td"},"is passed"),", ",(0,r.yg)("a",{parentName:"td",href:"/docs/next/external-type-declaration"},"the class/enum annotated with ",(0,r.yg)("inlineCode",{parentName:"a"},"#[Type]")," becomes a service"),".")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"default"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"bool"),(0,r.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,r.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"external"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"bool"),(0,r.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,r.yg)("a",{parentName:"td",href:"/docs/next/external-type-declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,r.yg)("h2",{id:"extendtype"},"#","[ExtendType]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," attribute is used to add fields to an existing GraphQL object type."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"class"),(0,r.yg)("td",{parentName:"tr",align:null},"see below"),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,r.yg)("a",{parentName:"td",href:"/docs/next/extend-type"},"The class annotated with ",(0,r.yg)("inlineCode",{parentName:"a"},"#[ExtendType]")," is a service"),".")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},"see below"),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,r.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,r.yg)("h2",{id:"input"},"#","[Input]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute is used to declare a GraphQL input type."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL input type generated. If not passed, the name of the class with suffix "Input" is used. If the class ends with "Input", the "Input" suffix is not added.')),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"description"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Description of the input type in the documentation. If not passed, PHP doc comment is used.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"default"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"bool"),(0,r.yg)("td",{parentName:"tr",align:null},"Name of the input type represented in your GraphQL schema. Defaults to ",(0,r.yg)("inlineCode",{parentName:"td"},"true")," ",(0,r.yg)("em",{parentName:"td"},"only if")," the name is not specified. If ",(0,r.yg)("inlineCode",{parentName:"td"},"name")," is specified, this will default to ",(0,r.yg)("inlineCode",{parentName:"td"},"false"),", so must also be included for ",(0,r.yg)("inlineCode",{parentName:"td"},"true")," when ",(0,r.yg)("inlineCode",{parentName:"td"},"name")," is used.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"update"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"bool"),(0,r.yg)("td",{parentName:"tr",align:null},"Determines if the the input represents a partial update. When set to ",(0,r.yg)("inlineCode",{parentName:"td"},"true")," all input fields will become optional and won't have default values thus won't be set on resolve if they are not specified in the query/mutation/subscription. This primarily applies to nullable fields.")))),(0,r.yg)("h2",{id:"field"},"#","[Field]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute is used to declare a GraphQL field."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties of classes annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Type]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[ExtendType]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Input]"),".\nWhen it's applied on private or protected property, public getter or/and setter method is expected in the class accordingly\nwhether it's used for output type or input type. For example if property name is ",(0,r.yg)("inlineCode",{parentName:"p"},"foo")," then getter should be ",(0,r.yg)("inlineCode",{parentName:"p"},"getFoo()")," or setter should be ",(0,r.yg)("inlineCode",{parentName:"p"},"setFoo($foo)"),". Setter can be omitted if property related to the field is present in the constructor with the same name."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"for"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string, array"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the field to be used only for specific output or input type(s). By default field is used for all possible declared types.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"description"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/input-types"},"inputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL input type of a query.")))),(0,r.yg)("h2",{id:"sourcefield"},"#","[SourceField]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[SourceField]")," attribute is used to declare a GraphQL field."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Type]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[ExtendType]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"phpType"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"description"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If it's empty PHP doc comment of the method in the source class is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,r.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"annotations"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"array\\"),(0,r.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "#',"[Logged]",'" or "#',"[Right]",'" attribute as class here.')))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Note"),": ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,r.yg)("h2",{id:"magicfield"},"#","[MagicField]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[MagicField]")," attribute is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,r.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Type]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[ExtendType]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("a",{parentName:"td",href:"/docs/next/custom-types"},"outputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no"),"(*)"),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"phpType"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no"),"(*)"),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"description"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Field description displayed in the GraphQL docs. If not set, no description will be shown.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"sourceName"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the property in the source class. If not set, the ",(0,r.yg)("inlineCode",{parentName:"td"},"name")," will be used to get property value.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"annotations"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"array\\"),(0,r.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "#',"[Logged]",'" or "#',"[Right]",'" attribute as class here.')))),(0,r.yg)("p",null,"(*) ",(0,r.yg)("strong",{parentName:"p"},"Note"),": ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,r.yg)("h2",{id:"prefetch"},"#","[Prefetch]"),(0,r.yg)("p",null,"Marks field parameter to be used for ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/prefetch-method"},"prefetching"),"."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": parameters of methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"callable"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"callable"),(0,r.yg)("td",{parentName:"tr",align:null},"Name of the prefetch method (in same class) or a full callable, either a static method or regular service from the container")))),(0,r.yg)("h2",{id:"logged"},"#","[Logged]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Logged]")," attribute is used to declare a Query/Mutation/Field is only visible to logged users."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),"."),(0,r.yg)("p",null,"This attribute allows no arguments."),(0,r.yg)("h2",{id:"right"},"#","[Right]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Right]")," attribute is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,r.yg)("h2",{id:"failwith"},"#","[FailWith]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[FailWith]")," attribute is used to declare a default value to return in the user is not authorized to see a specific\nquery/mutation/subscription/field (according to the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Logged]")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Right]")," attributes)."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," and one of ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Logged]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Right]")," attributes."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"value"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"mixed"),(0,r.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,r.yg)("h2",{id:"hideifunauthorized"},"#","[HideIfUnauthorized]"),(0,r.yg)("div",{class:"alert alert--warning"},"This attribute only works when a Schema is used to handle exactly one use request. If you serve your GraphQL API from long-running standalone servers (like Laravel Octane, Swoole, RoadRunner etc) and share the same Schema instance between multiple requests, please avoid using #[HideIfUnauthorized]."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[HideIfUnauthorized]")," attribute is used to completely hide the query/mutation/subscription/field if the user is not authorized\nto access it (according to the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Logged]")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Right]")," attributes)."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," and one of ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Logged]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Right]")," attributes."),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"#[HideIfUnauthorized]")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"#[FailWith]")," are mutually exclusive."),(0,r.yg)("h2",{id:"injectuser"},"#","[InjectUser]"),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[InjectUser]")," attribute to inject an instance of the current user logged in into a parameter of your\nquery/mutation/subscription/field."),(0,r.yg)("p",null,"See ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/authentication-authorization"},"the authentication and authorization page")," for more details."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,r.yg)("h2",{id:"security"},"#","[Security]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Security]")," attribute can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,r.yg)("p",null,"See ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/fine-grained-security"},"the fine grained security page")," for more details."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods or properties annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"default")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,r.yg)("h2",{id:"factory"},"#","[Factory]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Factory]")," attribute is used to declare a factory that turns GraphQL input types into objects."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"default"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"bool"),(0,r.yg)("td",{parentName:"tr",align:null},"If ",(0,r.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,r.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,r.yg)("a",{parentName:"td",href:"/docs/next/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,r.yg)("inlineCode",{parentName:"a"},"#[Parameter]")," attribute"),".")))),(0,r.yg)("h2",{id:"useinputtype"},"#","[UseInputType]"),(0,r.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"inputType")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,r.yg)("h2",{id:"decorate"},"#","[Decorate]"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Decorate]")," attribute is used ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/extend-input-type"},"to extend/modify/decorate an input type declared with the ",(0,r.yg)("inlineCode",{parentName:"a"},"#[Factory]")," attribute"),"."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,r.yg)("h2",{id:"autowire"},"#","[Autowire]"),(0,r.yg)("p",null,(0,r.yg)("a",{parentName:"p",href:"/docs/next/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,r.yg)("p",null,"Useful to inject services directly into ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," method arguments."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"identifier")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,r.yg)("h2",{id:"hideparameter"},"#","[HideParameter]"),(0,r.yg)("p",null,"Removes ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,r.yg)("h2",{id:"cost"},"#","[Cost]"),(0,r.yg)("p",null,"Sets complexity and multipliers on fields for ",(0,r.yg)("a",{parentName:"p",href:"/docs/next/operation-complexity#static-request-analysis"},"automatic query complexity"),"."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"complexity")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"int"),(0,r.yg)("td",{parentName:"tr",align:null},"Complexity for that field")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"multipliers")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"array\\"),(0,r.yg)("td",{parentName:"tr",align:null},"Names of fields by value of which complexity will be multiplied")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"defaultMultiplier")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"int"),(0,r.yg)("td",{parentName:"tr",align:null},"Default multiplier value if all multipliers are missing/null")))),(0,r.yg)("h2",{id:"validate"},"#","[Validate]"),(0,r.yg)("div",{class:"alert alert--info"},"This attribute is only available in the GraphQLite Laravel package"),(0,r.yg)("p",null,(0,r.yg)("a",{parentName:"p",href:"/docs/next/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Factory]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Decorator]")," attribute."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"rule")),(0,r.yg)("td",{parentName:"tr",align:null},"*yes"),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,r.yg)("p",null,"Sample:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Validate(for: "$email", rule: "email|unique:users")]\n')),(0,r.yg)("h2",{id:"assertion"},"#","[Assertion]"),(0,r.yg)("p",null,(0,r.yg)("a",{parentName:"p",href:"/docs/next/validation"},"Validates a user input"),"."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Assertion]")," attribute is available in the ",(0,r.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Query]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Mutation]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Factory]")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Decorator]")," attribute."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"for")),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"yes")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"constraint")),(0,r.yg)("td",{parentName:"tr",align:null},"*yes"),(0,r.yg)("td",{parentName:"tr",align:null},"annotation"),(0,r.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation attributes.")))),(0,r.yg)("h2",{id:"enumtype"},(0,r.yg)("del",{parentName:"h2"},"@EnumType")),(0,r.yg)("p",null,(0,r.yg)("em",{parentName:"p"},"Deprecated: Use ",(0,r.yg)("a",{parentName:"em",href:"https://www.php.net/manual/en/language.types.enumerations.php"},"PHP 8.1's native Enums")," instead with a ",(0,r.yg)("a",{parentName:"em",href:"#type-annotation"},"#[Type]"),".")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,r.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,r.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,r.yg)("table",null,(0,r.yg)("thead",{parentName:"table"},(0,r.yg)("tr",{parentName:"thead"},(0,r.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,r.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,r.yg)("th",{parentName:"tr",align:null},"Type"),(0,r.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,r.yg)("tbody",{parentName:"table"},(0,r.yg)("tr",{parentName:"tbody"},(0,r.yg)("td",{parentName:"tr",align:null},"name"),(0,r.yg)("td",{parentName:"tr",align:null},(0,r.yg)("em",{parentName:"td"},"no")),(0,r.yg)("td",{parentName:"tr",align:null},"string"),(0,r.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b26a5b84.94499d93.js b/assets/js/b26a5b84.c51fb3ba.js similarity index 99% rename from assets/js/b26a5b84.94499d93.js rename to assets/js/b26a5b84.c51fb3ba.js index 82d9f16f46..dc3fc40027 100644 --- a/assets/js/b26a5b84.94499d93.js +++ b/assets/js/b26a5b84.c51fb3ba.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1481],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},75718:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend_type",title:"Extending a type",sidebar_label:"Extending a type",original_id:"extend_type"},s=void 0,u={unversionedId:"extend_type",id:"version-4.1/extend_type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-4.1/extend_type.mdx",sourceDirName:".",slug:"/extend_type",permalink:"/docs/4.1/extend_type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/extend_type.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend_type",title:"Extending a type",sidebar_label:"Extending a type",original_id:"extend_type"},sidebar:"version-4.1/docs",previous:{title:"Autowiring services",permalink:"/docs/4.1/autowiring"},next:{title:"External type declaration",permalink:"/docs/4.1/external_type_declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1481],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},75718:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend_type",title:"Extending a type",sidebar_label:"Extending a type",original_id:"extend_type"},s=void 0,u={unversionedId:"extend_type",id:"version-4.1/extend_type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-4.1/extend_type.mdx",sourceDirName:".",slug:"/extend_type",permalink:"/docs/4.1/extend_type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/extend_type.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend_type",title:"Extending a type",sidebar_label:"Extending a type",original_id:"extend_type"},sidebar:"version-4.1/docs",previous:{title:"Autowiring services",permalink:"/docs/4.1/autowiring"},next:{title:"External type declaration",permalink:"/docs/4.1/external_type_declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b2d9540a.f5cddfbb.js b/assets/js/b2d9540a.2a7a8831.js similarity index 79% rename from assets/js/b2d9540a.f5cddfbb.js rename to assets/js/b2d9540a.2a7a8831.js index 2a4e32c029..0cd96a0d4b 100644 --- a/assets/js/b2d9540a.f5cddfbb.js +++ b/assets/js/b2d9540a.2a7a8831.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1084],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),i=a(20053);const o={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,i.A)(o.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>N});var n=a(58168),i=a(96540),o=a(20053),r=a(23104),l=a(56347),s=a(57485),u=a(31682),d=a(89466);function p(e){return function(e){return i.Children.map(e,(e=>{if(!e||(0,i.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:i}}=e;return{value:t,label:a,attributes:n,default:i}}))}function c(e){const{values:t,children:a}=e;return(0,i.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,i.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function m(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=c(e),[r,l]=(0,i.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,u]=h({queryString:a,groupId:n}),[p,m]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,d.Dv)(a);return[n,(0,i.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),g=(()=>{const e=s??p;return y({value:e,tabValues:o})?e:null})();(0,i.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:r,selectValue:(0,i.useCallback)((e=>{if(!y({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),m(e)}),[u,m,o]),tabValues:o}}var g=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=d.indexOf(t),n=u[a].value;n!==l&&(p(t),s(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=d.indexOf(e.currentTarget)+1;t=d[a]??d[0];break}case"ArrowLeft":{const a=d.indexOf(e.currentTarget)-1;t=d[a]??d[d.length-1];break}}t?.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:r}=e;return i.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>d.push(e),onKeyDown:y,onClick:c},r,{className:(0,o.A)("tabs__item",v.tabItem,r?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,i.cloneElement)(e,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,i.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=m(e);return i.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},i.createElement(f,(0,n.A)({},e,t)),i.createElement(b,(0,n.A)({},e,t)))}function N(e){const t=(0,g.A)();return i.createElement(w,(0,n.A)({key:String(t)},e))}},90837:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=a(58168),i=(a(96540),a(15680)),o=(a(67443),a(11470)),r=a(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-6.0/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-6.0/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/6.0/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/validation.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"docs",previous:{title:"Error handling",permalink:"/docs/6.0/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/6.0/authentication-authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3},{value:"Custom InputType Validation",id:"custom-inputtype-validation",level:2}],c={toc:p},y="wrapper";function h(e){let{components:t,...a}=e;return(0,i.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,i.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,i.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,i.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,i.yg)("a",{parentName:"p",href:"/docs/6.0/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,i.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,i.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,i.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,i.yg)("pre",{parentName:"li"},(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,i.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,i.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,i.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,i.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,i.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,i.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,i.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,i.yg)("p",null,"If the data entered by the user is ",(0,i.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,i.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\GraphQLite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,i.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,i.yg)("p",null,"You can also pass an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,i.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"),(0,i.yg)("h2",{id:"custom-inputtype-validation"},"Custom InputType Validation"),(0,i.yg)("p",null,"GraphQLite also supports a fully custom validation implementation for all input types defined with an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation or PHP8 ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute. This offers a way to validate input types before they're available as a method parameter of your query and mutation controllers. This way, when you're using your query or mutation controllers, you can feel confident that your input type objects have already been validated."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("p",null,"It's important to note that this validation implementation does not validate input types created with a factory. If you are creating an input type with a factory, or using primitive parameters in your query/mutation controllers, you should be sure to validate these independently. This is strictly for input type objects."),(0,i.yg)("p",null,"You can use one of the framework validation libraries listed above or implement your own validation for these cases. If you're using input type objects for most all of your query and mutation controllers, then there is little additional validation concerns with regards to user input. There are many reasons why you should consider defaulting to an InputType object, as opposed to individual arguments, for your queries and mutations. This is just one additional perk.")),(0,i.yg)("p",null,"To get started with validation on input types defined by an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation, you'll first need to register your validator with the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$factory = new SchemaFactory($cache, $this->container);\n$factory->addControllerNamespace('App\\\\Controllers');\n$factory->addTypeNamespace('App');\n// Register your validator\n$factory->setInputTypeValidator($this->container->get('your_validator'));\n$factory->createSchema();\n")),(0,i.yg)("p",null,"Your input type validator must implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Types\\InputTypeValidatorInterface"),", as shown below:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"interface InputTypeValidatorInterface\n{\n /**\n * Checks to see if the Validator is currently enabled.\n */\n public function isEnabled(): bool;\n\n /**\n * Performs the validation of the InputType.\n *\n * @param object $input The input type object to validate\n */\n public function validate(object $input): void;\n}\n")),(0,i.yg)("p",null,"The interface is quite simple. Handle all of your own validation logic in the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method. For example, you might use Symfony's annotation based validation in addition to some other custom validation logic. It's really up to you on how you wish to handle your own validation. The ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method will receive the input type object populated with the user input."),(0,i.yg)("p",null,"You'll notice that the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method has a ",(0,i.yg)("inlineCode",{parentName:"p"},"void")," return. The purpose here is to encourage you to throw an Exception or handle validation output however you best see fit. GraphQLite does it's best to stay out of your way and doesn't make attempts to handle validation output. You can, however, throw an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")," as usual (see ",(0,i.yg)("a",{parentName:"p",href:"error-handling"},"Error Handling")," for more details)."),(0,i.yg)("p",null,"Also available is the ",(0,i.yg)("inlineCode",{parentName:"p"},"isEnabled")," method. This method is checked before executing validation on an InputType being resolved. You can work out your own logic to selectively enable or disable validation through this method. In most cases, you can simply return ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," to keep it always enabled."),(0,i.yg)("p",null,"And that's it, now, anytime an input type is resolved, the validator will be executed on that input type immediately after it has been hydrated with user input."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1084],{19365:(e,a,t)=>{t.d(a,{A:()=>r});var n=t(96540),i=t(20053);const o={tabItem:"tabItem_Ymn6"};function r(e){let{children:a,hidden:t,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,i.A)(o.tabItem,r),hidden:t},a)}},11470:(e,a,t)=>{t.d(a,{A:()=>N});var n=t(58168),i=t(96540),o=t(20053),r=t(23104),l=t(56347),s=t(57485),u=t(31682),d=t(89466);function p(e){return function(e){return i.Children.map(e,(e=>{if(!e||(0,i.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:t,attributes:n,default:i}}=e;return{value:a,label:t,attributes:n,default:i}}))}function c(e){const{values:a,children:t}=e;return(0,i.useMemo)((()=>{const e=a??p(t);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,t])}function y(e){let{value:a,tabValues:t}=e;return t.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:t}=e;const n=(0,l.W6)(),o=function(e){let{queryString:a=!1,groupId:t}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:a,groupId:t});return[(0,s.aZ)(o),(0,i.useCallback)((e=>{if(!o)return;const a=new URLSearchParams(n.location.search);a.set(o,e),n.replace({...n.location,search:a.toString()})}),[o,n])]}function m(e){const{defaultValue:a,queryString:t=!1,groupId:n}=e,o=c(e),[r,l]=(0,i.useState)((()=>function(e){let{defaultValue:a,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!y({value:a,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const n=t.find((e=>e.default))??t[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:a,tabValues:o}))),[s,u]=h({queryString:t,groupId:n}),[p,m]=function(e){let{groupId:a}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(a),[n,o]=(0,d.Dv)(t);return[n,(0,i.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:n}),g=(()=>{const e=s??p;return y({value:e,tabValues:o})?e:null})();(0,i.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:r,selectValue:(0,i.useCallback)((e=>{if(!y({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),m(e)}),[u,m,o]),tabValues:o}}var g=t(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:a,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),c=e=>{const a=e.currentTarget,t=d.indexOf(a),n=u[t].value;n!==l&&(p(a),s(n))},y=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const t=d.indexOf(e.currentTarget)+1;a=d[t]??d[0];break}case"ArrowLeft":{const t=d.indexOf(e.currentTarget)-1;a=d[t]??d[d.length-1];break}}a?.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},a)},u.map((e=>{let{value:a,label:t,attributes:r}=e;return i.createElement("li",(0,n.A)({role:"tab",tabIndex:l===a?0:-1,"aria-selected":l===a,key:a,ref:e=>d.push(e),onKeyDown:y,onClick:c},r,{className:(0,o.A)("tabs__item",v.tabItem,r?.className,{"tabs__item--active":l===a})}),t??a)})))}function b(e){let{lazy:a,children:t,selectedValue:n}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(a){const e=o.find((e=>e.props.value===n));return e?(0,i.cloneElement)(e,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},o.map(((e,a)=>(0,i.cloneElement)(e,{key:a,hidden:e.props.value!==n}))))}function w(e){const a=m(e);return i.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},i.createElement(f,(0,n.A)({},e,a)),i.createElement(b,(0,n.A)({},e,a)))}function N(e){const a=(0,g.A)();return i.createElement(w,(0,n.A)({key:String(a)},e))}},90837:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>d,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var n=t(58168),i=(t(96540),t(15680)),o=(t(67443),t(11470)),r=t(19365);const l={id:"validation",title:"Validation",sidebar_label:"User input validation"},s=void 0,u={unversionedId:"validation",id:"version-6.0/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-6.0/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/6.0/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/validation.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"docs",previous:{title:"Error handling",permalink:"/docs/6.0/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/6.0/authentication-authorization"}},d={},p=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3},{value:"Custom InputType Validation",id:"custom-inputtype-validation",level:2}],c={toc:p},y="wrapper";function h(e){let{components:a,...t}=e;return(0,i.yg)(y,(0,n.A)({},c,t,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,i.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,i.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,i.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,i.yg)("a",{parentName:"p",href:"/docs/6.0/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,i.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,i.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,i.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,i.yg)("pre",{parentName:"li"},(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,i.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,i.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,i.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n"))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n /**\n * @Mutation\n */\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")))),(0,i.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,i.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,i.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n'))),(0,i.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n /**\n * @Assert\\Email(\n * message = "The email \'{{ value }}\' is not a valid email.",\n * checkMX = true\n * )\n */\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n * @Assert\\NotCompromisedPassword\n */\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')))),(0,i.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,i.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,i.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,i.yg)("p",null,"If the data entered by the user is ",(0,i.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,i.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\GraphQLite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,i.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,i.yg)("p",null,"You can also pass an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,i.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"),(0,i.yg)("h2",{id:"custom-inputtype-validation"},"Custom InputType Validation"),(0,i.yg)("p",null,"GraphQLite also supports a fully custom validation implementation for all input types defined with an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation or PHP8 ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute. This offers a way to validate input types before they're available as a method parameter of your query and mutation controllers. This way, when you're using your query or mutation controllers, you can feel confident that your input type objects have already been validated."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("p",null,"It's important to note that this validation implementation does not validate input types created with a factory. If you are creating an input type with a factory, or using primitive parameters in your query/mutation controllers, you should be sure to validate these independently. This is strictly for input type objects."),(0,i.yg)("p",null,"You can use one of the framework validation libraries listed above or implement your own validation for these cases. If you're using input type objects for most all of your query and mutation controllers, then there is little additional validation concerns with regards to user input. There are many reasons why you should consider defaulting to an InputType object, as opposed to individual arguments, for your queries and mutations. This is just one additional perk.")),(0,i.yg)("p",null,"To get started with validation on input types defined by an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation, you'll first need to register your validator with the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$factory = new SchemaFactory($cache, $this->container);\n$factory->addControllerNamespace('App\\\\Controllers');\n$factory->addTypeNamespace('App');\n// Register your validator\n$factory->setInputTypeValidator($this->container->get('your_validator'));\n$factory->createSchema();\n")),(0,i.yg)("p",null,"Your input type validator must implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Types\\InputTypeValidatorInterface"),", as shown below:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"interface InputTypeValidatorInterface\n{\n /**\n * Checks to see if the Validator is currently enabled.\n */\n public function isEnabled(): bool;\n\n /**\n * Performs the validation of the InputType.\n *\n * @param object $input The input type object to validate\n */\n public function validate(object $input): void;\n}\n")),(0,i.yg)("p",null,"The interface is quite simple. Handle all of your own validation logic in the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method. For example, you might use Symfony's annotation based validation in addition to some other custom validation logic. It's really up to you on how you wish to handle your own validation. The ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method will receive the input type object populated with the user input."),(0,i.yg)("p",null,"You'll notice that the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method has a ",(0,i.yg)("inlineCode",{parentName:"p"},"void")," return. The purpose here is to encourage you to throw an Exception or handle validation output however you best see fit. GraphQLite does it's best to stay out of your way and doesn't make attempts to handle validation output. You can, however, throw an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")," as usual (see ",(0,i.yg)("a",{parentName:"p",href:"error-handling"},"Error Handling")," for more details)."),(0,i.yg)("p",null,"Also available is the ",(0,i.yg)("inlineCode",{parentName:"p"},"isEnabled")," method. This method is checked before executing validation on an InputType being resolved. You can work out your own logic to selectively enable or disable validation through this method. In most cases, you can simply return ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," to keep it always enabled."),(0,i.yg)("p",null,"And that's it, now, anytime an input type is resolved, the validator will be executed on that input type immediately after it has been hydrated with user input."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b35d1284.5b3a1e1c.js b/assets/js/b35d1284.ce1e8749.js similarity index 92% rename from assets/js/b35d1284.5b3a1e1c.js rename to assets/js/b35d1284.ce1e8749.js index 91e254eddd..510c7f4c49 100644 --- a/assets/js/b35d1284.5b3a1e1c.js +++ b/assets/js/b35d1284.ce1e8749.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[552],{49395:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>l});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-5.0/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-5.0/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/5.0/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/universal-service-providers.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"version-5.0/docs",previous:{title:"Laravel package",permalink:"/docs/5.0/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/5.0/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/5.0/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[552],{49395:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>s,toc:()=>p});var n=i(58168),t=(i(96540),i(15680));i(67443);const a={id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},o=void 0,s={unversionedId:"universal-service-providers",id:"version-5.0/universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-5.0/universal-service-providers.md",sourceDirName:".",slug:"/universal-service-providers",permalink:"/docs/5.0/universal-service-providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/universal-service-providers.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"universal-service-providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers"},sidebar:"version-5.0/docs",previous:{title:"Laravel package",permalink:"/docs/5.0/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/5.0/other-frameworks"}},l={},p=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:p},d="wrapper";function h(e){let{components:r,...i}=e;return(0,t.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,t.yg)("p",null,"If your framework is compatible with ",(0,t.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we provide a ",(0,t.yg)("a",{parentName:"p",href:"/docs/5.0/other-frameworks"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,t.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,t.yg)("p",null,(0,t.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,t.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n\n// or if you want the PSR-15 middleware:\n\n$middleware = $container->get(Psr15GraphQLMiddlewareBuilder::class);\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b370b50c.24d426da.js b/assets/js/b370b50c.f9e9f1f7.js similarity index 98% rename from assets/js/b370b50c.24d426da.js rename to assets/js/b370b50c.f9e9f1f7.js index 10487e2008..5f441b82f4 100644 --- a/assets/js/b370b50c.24d426da.js +++ b/assets/js/b370b50c.f9e9f1f7.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5747],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>A});var n=a(58168),r=a(96540),i=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(n.location.search);t.set(i,e),n.replace({...n.location,search:t.toString()})}),[i,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,i]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:n}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,t)),r.createElement(b,(0,n.A)({},e,t)))}function A(e){const t=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},31332:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),i=(a(67443),a(11470)),o=a(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-3.0/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-3.0/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/3.0/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/autowiring.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(h,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null," #[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null," #[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend_type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5747],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>A});var n=a(58168),r=a(96540),i=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(n.location.search);t.set(i,e),n.replace({...n.location,search:t.toString()})}),[i,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,i]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:n}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,t)),r.createElement(b,(0,n.A)({},e,t)))}function A(e){const t=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},31332:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),i=(a(67443),a(11470)),o=a(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-3.0/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-3.0/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/3.0/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/autowiring.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(h,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null," #[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null," #[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend_type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b42f5805.f52b7c27.js b/assets/js/b42f5805.17436cba.js similarity index 99% rename from assets/js/b42f5805.f52b7c27.js rename to assets/js/b42f5805.17436cba.js index 7d8b9a9db1..cafb22fb6a 100644 --- a/assets/js/b42f5805.f52b7c27.js +++ b/assets/js/b42f5805.17436cba.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6427],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),o=t(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),o=t(96540),i=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:o}}=e;return{value:n,label:t,attributes:a,default:o}}))}function d(e){const{values:n,children:t}=e;return(0,o.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,o.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,o.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,n)),o.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return o.createElement(v,(0,a.A)({key:String(n)},e))}},15726:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),o=(t(96540),t(15680)),i=(t(67443),t(11470)),r=t(19365);const l={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},u=void 0,s={unversionedId:"external-type-declaration",id:"version-4.3/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-4.3/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/4.3/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/external-type-declaration.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"version-4.3/docs",previous:{title:"Extending a type",permalink:"/docs/4.3/extend-type"},next:{title:"Input types",permalink:"/docs/4.3/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,o.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),")."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/4.3/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/4.3/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6427],{19365:(e,n,t)=>{t.d(n,{A:()=>r});var a=t(96540),o=t(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:n,hidden:t,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>A});var a=t(58168),o=t(96540),i=t(20053),r=t(23104),l=t(56347),u=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:o}}=e;return{value:n,label:t,attributes:a,default:o}}))}function d(e){const{values:n,children:t}=e;return(0,o.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function y(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,l.W6)(),i=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,u.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function g(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,i=d(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!y({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:i}))),[u,s]=h({queryString:t,groupId:a}),[p,g]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,i]=(0,c.Dv)(t);return[a,(0,o.useCallback)((e=>{t&&i.set(e)}),[t,i])]}({groupId:a}),m=(()=>{const e=u??p;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),s(e),g(e)}),[s,g,i]),tabValues:i}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),d=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==l&&(p(n),u(a))},y=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:y,onClick:d},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===n})}),t??n)})))}function T(e){let{lazy:n,children:t,selectedValue:a}=e;const i=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,n)=>(0,o.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function v(e){const n=g(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,n)),o.createElement(T,(0,a.A)({},e,n)))}function A(e){const n=(0,m.A)();return o.createElement(v,(0,a.A)({key:String(n)},e))}},15726:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});var a=t(58168),o=(t(96540),t(15680)),i=(t(67443),t(11470)),r=t(19365);const l={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},u=void 0,s={unversionedId:"external-type-declaration",id:"version-4.3/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-4.3/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/4.3/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/external-type-declaration.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"version-4.3/docs",previous:{title:"Extending a type",permalink:"/docs/4.3/extend-type"},next:{title:"Input types",permalink:"/docs/4.3/input-types"}},c={},p=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],d={toc:p},y="wrapper";function h(e){let{components:n,...t}=e;return(0,o.yg)(y,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field()\n */\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")))),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price")\n */\nclass ProductType\n{\n}\n')))),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),")."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n'))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n/**\n * @Type()\n * @MagicField(name="name", outputType="String!")\n * @MagicField(name="price", outputType="Float")\n */\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')))),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/4.3/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/4.3/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,o.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n"))),(0,o.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n/**\n * @Type(class=Product::class)\n */\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b4657038.94094889.js b/assets/js/b4657038.b198b399.js similarity index 97% rename from assets/js/b4657038.94094889.js rename to assets/js/b4657038.b198b399.js index 7b8ec5ad8d..7a460680f2 100644 --- a/assets/js/b4657038.94094889.js +++ b/assets/js/b4657038.b198b399.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1886],{94936:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},r=void 0,s={unversionedId:"doctrine-annotations-attributes",id:"version-3.0/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-3.0/doctrine_annotations_attributes.md",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/3.0/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/doctrine_annotations_attributes.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"}},l={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],u={toc:p},d="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(d,(0,a.A)({},u,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,i.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in GraphQLite 5.0"),(0,i.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support.\nThis was the purpose of the ',(0,i.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,i.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"Please note that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,i.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,i.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,i.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,i.yg)("div",{class:"alert alert--info"},(0,i.yg)("strong",null,"Heads up!"),"Some IDEs provide support for Doctrine annotations:",(0,i.yg)("ul",null,(0,i.yg)("li",null,"PhpStorm via the ",(0,i.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,i.yg)("li",null,"Eclipse via the ",(0,i.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,i.yg)("li",null,"Netbeans has native support")),(0,i.yg)("p",null,"We strongly recommend using an IDE that has Doctrine annotations support.")),(0,i.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,i.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,i.yg)("p",null,"The same code can be written this way:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,i.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,i.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,i.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,i.yg)("p",null,"They support the same attributes too."),(0,i.yg)("p",null,"A few notable differences:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,i.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,i.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,i.yg)("p",null,"Let's take an example with the ",(0,i.yg)("a",{parentName:"p",href:"/docs/3.0/autowiring"},(0,i.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 7+")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 8")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1886],{94936:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},r=void 0,s={unversionedId:"doctrine-annotations-attributes",id:"version-3.0/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-3.0/doctrine_annotations_attributes.md",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/3.0/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/doctrine_annotations_attributes.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"}},l={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],u={toc:p},d="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(d,(0,a.A)({},u,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,i.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in GraphQLite 5.0"),(0,i.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support.\nThis was the purpose of the ',(0,i.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,i.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"Please note that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,i.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,i.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,i.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,i.yg)("div",{class:"alert alert--info"},(0,i.yg)("strong",null,"Heads up!"),"Some IDEs provide support for Doctrine annotations:",(0,i.yg)("ul",null,(0,i.yg)("li",null,"PhpStorm via the ",(0,i.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,i.yg)("li",null,"Eclipse via the ",(0,i.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,i.yg)("li",null,"Netbeans has native support")),(0,i.yg)("p",null,"We strongly recommend using an IDE that has Doctrine annotations support.")),(0,i.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,i.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,i.yg)("p",null,"The same code can be written this way:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,i.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,i.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,i.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,i.yg)("p",null,"They support the same attributes too."),(0,i.yg)("p",null,"A few notable differences:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,i.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,i.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,i.yg)("p",null,"Let's take an example with the ",(0,i.yg)("a",{parentName:"p",href:"/docs/3.0/autowiring"},(0,i.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 7+")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 8")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b4aea2ce.efb40527.js b/assets/js/b4aea2ce.310f4b7d.js similarity index 96% rename from assets/js/b4aea2ce.efb40527.js rename to assets/js/b4aea2ce.310f4b7d.js index 64c9e61a58..4193ba3c93 100644 --- a/assets/js/b4aea2ce.efb40527.js +++ b/assets/js/b4aea2ce.310f4b7d.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5138],{68781:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>i,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var r=n(58168),t=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},i=void 0,o={unversionedId:"laravel-package",id:"version-3.0/laravel-package",title:"Getting started with Laravel",description:"The GraphQLite-Laravel package is compatible with Laravel 5.x.",source:"@site/versioned_docs/version-3.0/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/3.0/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/laravel-package.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},sidebar:"version-3.0/docs",previous:{title:"Symfony bundle",permalink:"/docs/3.0/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/3.0/universal_service_providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2}],d={toc:s},g="wrapper";function c(e){let{components:a,...n}=e;return(0,t.yg)(g,(0,r.A)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,t.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,t.yg)("strong",{parentName:"p"},"Laravel 5.x"),"."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,t.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ php artisan vendor:publish --provider=TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider\n")),(0,t.yg)("p",null,"You can then configure the library by editing ",(0,t.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"config/graphqlite.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => '/graphql'\n];\n")),(0,t.yg)("p",null,"The debug parameters are detailed in the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,t.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,t.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing GraphQL Playground"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,t.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,t.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5138],{68781:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>i,default:()=>c,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var r=n(58168),t=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},i=void 0,o={unversionedId:"laravel-package",id:"version-3.0/laravel-package",title:"Getting started with Laravel",description:"The GraphQLite-Laravel package is compatible with Laravel 5.x.",source:"@site/versioned_docs/version-3.0/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/3.0/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/laravel-package.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package",original_id:"laravel-package"},sidebar:"version-3.0/docs",previous:{title:"Symfony bundle",permalink:"/docs/3.0/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/3.0/universal_service_providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2}],d={toc:s},g="wrapper";function c(e){let{components:a,...n}=e;return(0,t.yg)(g,(0,r.A)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,t.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,t.yg)("strong",{parentName:"p"},"Laravel 5.x"),"."),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,t.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ php artisan vendor:publish --provider=TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider\n")),(0,t.yg)("p",null,"You can then configure the library by editing ",(0,t.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,t.yg)("p",null,(0,t.yg)("strong",{parentName:"p"},"config/graphqlite.php")),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => '/graphql'\n];\n")),(0,t.yg)("p",null,"The debug parameters are detailed in the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,t.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,t.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing GraphQL Playground"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,t.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,t.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b5d0ac54.11ff1282.js b/assets/js/b5d0ac54.f653835d.js similarity index 99% rename from assets/js/b5d0ac54.11ff1282.js rename to assets/js/b5d0ac54.f653835d.js index 01fbfd1261..c3a4449a44 100644 --- a/assets/js/b5d0ac54.11ff1282.js +++ b/assets/js/b5d0ac54.f653835d.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3343],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var t=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),l=a(20053),i=a(23104),s=a(56347),p=a(57485),c=a(31682),o=a(89466);function u(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function m(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function d(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[p,c]=g({queryString:a,groupId:t}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=p??u;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),c(e),y(e)}),[c,y,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:p,tabValues:c}=e;const o=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=c[a].value;t!==s&&(u(n),p(t))},d=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:d,onClick:m},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function N(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(v,(0,t.A)({},e,n)))}function T(e){const n=(0,h.A)();return r.createElement(N,(0,t.A)({key:String(n)},e))}},15753:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>p,default:()=>g,frontMatter:()=>s,metadata:()=>c,toc:()=>u});var t=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const s={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},p=void 0,c={unversionedId:"inheritance-interfaces",id:"version-6.0/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-6.0/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/6.0/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/inheritance-interfaces.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"docs",previous:{title:"Input types",permalink:"/docs/6.0/input-types"},next:{title:"Error handling",permalink:"/docs/6.0/error-handling"}},o={},u=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],m={toc:u},d="wrapper";function g(e){let{components:n,...a}=e;return(0,r.yg)(d,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,r.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,r.yg)("p",null,"Let's say you have two classes, ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,r.yg)("p",null,"Written in ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,r.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,r.yg)("p",null,"The GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,r.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,r.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,r.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,r.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will translate in GraphQL schema as:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,r.yg)("p",null,"Please note that you do not need to put the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,r.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,r.yg)("p",null,"You don't have to explicitly put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")))),(0,r.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,r.yg)("p",null,"In the example above, because the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,r.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3343],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var t=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),l=a(20053),i=a(23104),s=a(56347),p=a(57485),c=a(31682),o=a(89466);function u(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function m(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function d(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[p,c]=g({queryString:a,groupId:t}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=p??u;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),c(e),y(e)}),[c,y,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:p,tabValues:c}=e;const o=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=c[a].value;t!==s&&(u(n),p(t))},d=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:d,onClick:m},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function N(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(v,(0,t.A)({},e,n)))}function T(e){const n=(0,h.A)();return r.createElement(N,(0,t.A)({key:String(n)},e))}},15753:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>p,default:()=>g,frontMatter:()=>s,metadata:()=>c,toc:()=>u});var t=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const s={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},p=void 0,c={unversionedId:"inheritance-interfaces",id:"version-6.0/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-6.0/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/6.0/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/inheritance-interfaces.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"docs",previous:{title:"Input types",permalink:"/docs/6.0/input-types"},next:{title:"Error handling",permalink:"/docs/6.0/error-handling"}},o={},u=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],m={toc:u},d="wrapper";function g(e){let{components:n,...a}=e;return(0,r.yg)(d,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,r.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,r.yg)("p",null,"Let's say you have two classes, ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,r.yg)("p",null,"Written in ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,r.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,r.yg)("p",null,"The GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,r.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,r.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,r.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,r.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will translate in GraphQL schema as:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,r.yg)("p",null,"Please note that you do not need to put the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,r.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,r.yg)("p",null,"You don't have to explicitly put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")))),(0,r.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,r.yg)("p",null,"In the example above, because the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,r.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b5d32d98.f0acdc5c.js b/assets/js/b5d32d98.bb7ca4aa.js similarity index 96% rename from assets/js/b5d32d98.f0acdc5c.js rename to assets/js/b5d32d98.bb7ca4aa.js index 22dff07826..9c1925fc81 100644 --- a/assets/js/b5d32d98.f0acdc5c.js +++ b/assets/js/b5d32d98.bb7ca4aa.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[481],{62062:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>s,contentTitle:()=>l,default:()=>h,frontMatter:()=>n,metadata:()=>r,toc:()=>p});var o=t(58168),i=(t(96540),t(15680));t(67443);const n={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},l=void 0,r={unversionedId:"file-uploads",id:"version-3.0/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-3.0/file_uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/3.0/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/file_uploads.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},sidebar:"version-3.0/docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/3.0/inheritance"},next:{title:"Pagination",permalink:"/docs/3.0/pagination"}},s={},p=[{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:2},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:2},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:2},{value:"Usage",id:"usage",level:2}],u={toc:p},d="wrapper";function h(e){let{components:a,...t}=e;return(0,i.yg)(d,(0,o.A)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,i.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,i.yg)("h2",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,i.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,i.yg)("h2",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,i.yg)("p",null,"In order to use this, you must first be sure that the ",(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,i.yg)("p",null,"Simply add ",(0,i.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,i.yg)("h2",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,i.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,i.yg)("h2",{id:"usage"},"Usage"),(0,i.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,i.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")),(0,i.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests."),(0,i.yg)("p",null,"See ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[481],{62062:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>s,contentTitle:()=>l,default:()=>h,frontMatter:()=>n,metadata:()=>r,toc:()=>p});var o=t(58168),i=(t(96540),t(15680));t(67443);const n={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},l=void 0,r={unversionedId:"file-uploads",id:"version-3.0/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-3.0/file_uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/3.0/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/file_uploads.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},sidebar:"version-3.0/docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/3.0/inheritance"},next:{title:"Pagination",permalink:"/docs/3.0/pagination"}},s={},p=[{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:2},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:2},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:2},{value:"Usage",id:"usage",level:2}],u={toc:p},d="wrapper";function h(e){let{components:a,...t}=e;return(0,i.yg)(d,(0,o.A)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,i.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,i.yg)("h2",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,i.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,i.yg)("h2",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,i.yg)("p",null,"In order to use this, you must first be sure that the ",(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,i.yg)("p",null,"Simply add ",(0,i.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,i.yg)("h2",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,i.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,i.yg)("h2",{id:"usage"},"Usage"),(0,i.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,i.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")),(0,i.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests."),(0,i.yg)("p",null,"See ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b6722b03.0f792d4a.js b/assets/js/b6722b03.74eb5003.js similarity index 98% rename from assets/js/b6722b03.0f792d4a.js rename to assets/js/b6722b03.74eb5003.js index 54e3a2d51a..f4f9338c5e 100644 --- a/assets/js/b6722b03.0f792d4a.js +++ b/assets/js/b6722b03.74eb5003.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[663],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),b=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==o&&(p(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,l.A)("tabs__item",y.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(f,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function T(e){const t=(0,b.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},70259:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},s=void 0,i={unversionedId:"index",id:"version-7.0.0/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-7.0.0/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/README.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"docs",next:{title:"Getting Started",permalink:"/docs/getting-started"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:p},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, subscriptions, mapping of arrays / iterators,\nfile uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[663],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),b=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==o&&(p(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,l.A)("tabs__item",y.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(f,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function T(e){const t=(0,b.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},70259:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},s=void 0,i={unversionedId:"index",id:"version-7.0.0/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-7.0.0/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/README.mdx",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"docs",next:{title:"Getting Started",permalink:"/docs/getting-started"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:p},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, subscriptions, mapping of arrays / iterators,\nfile uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b6a6a31f.59d36e4d.js b/assets/js/b6a6a31f.587f0446.js similarity index 97% rename from assets/js/b6a6a31f.59d36e4d.js rename to assets/js/b6a6a31f.587f0446.js index 28713614dc..147eb8ac9f 100644 --- a/assets/js/b6a6a31f.59d36e4d.js +++ b/assets/js/b6a6a31f.587f0446.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4955],{65995:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>o,toc:()=>h});var a=n(58168),r=(n(96540),n(15680));n(67443);const s={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,o={unversionedId:"prefetch-method",id:"prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/docs/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/next/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/prefetch-method.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"docs",previous:{title:"Query plan",permalink:"/docs/next/query-plan"},next:{title:"Automatic persisted queries",permalink:"/docs/next/automatic-persisted-queries"}},l={},h=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],p={toc:h},c="wrapper";function d(e){let{components:t,...n}=e;return(0,r.yg)(c,(0,a.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/next/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field]\n public function getUser(#[Prefetch("prefetchUsers")] $prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as first argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public static function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')),(0,r.yg)("p",null,"When a ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Prefetch]")," attribute is detected on a parameter of ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute, the method is called automatically.\nThe prefetch callable must be one of the following:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a static method in the same class: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch('prefetchMethod')]")),(0,r.yg)("li",{parentName:"ul"},"a static method in a different class: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch([OtherClass::class, 'prefetchMethod')]")),(0,r.yg)("li",{parentName:"ul"},"a non-static method in a different class, resolvable through the container: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch([OtherService::class, 'prefetchMethod'])]"),"\nThe first argument of the method is always an array of instances of the main type. It can return absolutely anything (mixed).")),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," annotated method OR/AND on the prefetch methods."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field]\n public function getComments(#[Prefetch("prefetchComments")] $prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public static function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4955],{65995:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>o,toc:()=>h});var a=n(58168),r=(n(96540),n(15680));n(67443);const s={id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},i=void 0,o={unversionedId:"prefetch-method",id:"prefetch-method",title:"Prefetching records",description:"The problem",source:"@site/docs/prefetch-method.mdx",sourceDirName:".",slug:"/prefetch-method",permalink:"/docs/next/prefetch-method",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/prefetch-method.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"prefetch-method",title:"Prefetching records",sidebar_label:"Prefetching records"},sidebar:"docs",previous:{title:"Query plan",permalink:"/docs/next/query-plan"},next:{title:"Automatic persisted queries",permalink:"/docs/next/automatic-persisted-queries"}},l={},h=[{value:"The problem",id:"the-problem",level:2},{value:"The "prefetch" method",id:"the-prefetch-method",level:2},{value:"Input arguments",id:"input-arguments",level:2}],p={toc:h},c="wrapper";function d(e){let{components:t,...n}=e;return(0,r.yg)(c,(0,a.A)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Consider a request where a user attached to a post must be returned:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n posts {\n id\n user {\n id\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of posts"),(0,r.yg)("li",{parentName:"ul"},"1 query per post to fetch the user")),(0,r.yg)("p",null,'Assuming we have "N" posts, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem.\nAssuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "posts" and "users".\nThis method is described in the ',(0,r.yg)("a",{parentName:"p",href:"/docs/next/query-plan"},'"analyzing the query plan" documentation'),"."),(0,r.yg)("p",null,"But this can be difficult to implement. This is also only useful for relational databases. If your data comes from a\nNoSQL database or from the cache, this will not help."),(0,r.yg)("p",null,"Instead, GraphQLite offers an easier to implement solution: the ability to fetch all fields from a given type at once."),(0,r.yg)("h2",{id:"the-prefetch-method"},'The "prefetch" method'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedUsers\n * @return User\n */\n #[Field]\n public function getUser(#[Prefetch("prefetchUsers")] $prefetchedUsers): User\n {\n // This method will receive the $prefetchedUsers as first argument. This is the return value of the "prefetchUsers" method below.\n // Using this prefetched list, it should be easy to map it to the post\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public static function prefetchUsers(iterable $posts)\n {\n // This function is called only once per GraphQL request\n // with the list of posts. You can fetch the list of users\n // associated with this posts in a single request,\n // for instance using a "IN" query in SQL or a multi-fetch\n // in your cache back-end.\n }\n}\n')),(0,r.yg)("p",null,"When a ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Prefetch]")," attribute is detected on a parameter of ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute, the method is called automatically.\nThe prefetch callable must be one of the following:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a static method in the same class: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch('prefetchMethod')]")),(0,r.yg)("li",{parentName:"ul"},"a static method in a different class: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch([OtherClass::class, 'prefetchMethod')]")),(0,r.yg)("li",{parentName:"ul"},"a non-static method in a different class, resolvable through the container: ",(0,r.yg)("inlineCode",{parentName:"li"},"#[Prefetch([OtherService::class, 'prefetchMethod'])]"),"\nThe first argument of the method is always an array of instances of the main type. It can return absolutely anything (mixed).")),(0,r.yg)("h2",{id:"input-arguments"},"Input arguments"),(0,r.yg)("p",null,"Field arguments can be set either on the ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Field]")," annotated method OR/AND on the prefetch methods."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\nclass PostType {\n /**\n * @param mixed $prefetchedComments\n * @return Comment[]\n */\n #[Field]\n public function getComments(#[Prefetch("prefetchComments")] $prefetchedComments): array\n {\n // ...\n }\n\n /**\n * @param Post[] $posts\n * @return mixed\n */\n public static function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)\n {\n // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed\n // as GraphQL arguments for the "comments" field.\n }\n}\n')))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b7442939.95fdc2e7.js b/assets/js/b7442939.8ab388f0.js similarity index 96% rename from assets/js/b7442939.95fdc2e7.js rename to assets/js/b7442939.8ab388f0.js index 36c1ca35e3..97e2a27d3c 100644 --- a/assets/js/b7442939.95fdc2e7.js +++ b/assets/js/b7442939.8ab388f0.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3781],{5488:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>g,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var t=n(58168),r=(n(96540),n(15680));n(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-4.3/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.3/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/4.3/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/argument-resolving.md",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"version-4.3/docs",previous:{title:"Custom annotations",permalink:"/docs/4.3/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/4.3/extend-input-type"}},s={},p=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},d="wrapper";function g(e){let{components:a,...n}=e;return(0,r.yg)(d,(0,t.A)({},m,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Autowire")," annotation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3781],{5488:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var t=n(58168),r=(n(96540),n(15680));n(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-4.3/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-4.3/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/4.3/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/argument-resolving.md",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"version-4.3/docs",previous:{title:"Custom annotations",permalink:"/docs/4.3/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/4.3/extend-input-type"}},s={},p=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},g="wrapper";function d(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},m,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Autowire")," annotation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b8487569.d2bb8d8a.js b/assets/js/b8487569.332c9ebe.js similarity index 99% rename from assets/js/b8487569.d2bb8d8a.js rename to assets/js/b8487569.332c9ebe.js index 8d3733013f..5d066aeb09 100644 --- a/assets/js/b8487569.d2bb8d8a.js +++ b/assets/js/b8487569.332c9ebe.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8526],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},68838:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},s=void 0,u={unversionedId:"extend-type",id:"version-4.3/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-4.3/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/4.3/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/extend-type.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"version-4.3/docs",previous:{title:"Autowiring services",permalink:"/docs/4.3/autowiring"},next:{title:"External type declaration",permalink:"/docs/4.3/external-type-declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8526],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},68838:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},s=void 0,u={unversionedId:"extend-type",id:"version-4.3/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-4.3/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/4.3/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/extend-type.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"version-4.3/docs",previous:{title:"Autowiring services",permalink:"/docs/4.3/autowiring"},next:{title:"External type declaration",permalink:"/docs/4.3/external-type-declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b94a1068.fbbebba7.js b/assets/js/b94a1068.29fbf944.js similarity index 97% rename from assets/js/b94a1068.fbbebba7.js rename to assets/js/b94a1068.29fbf944.js index 310ca088fd..e177361a04 100644 --- a/assets/js/b94a1068.fbbebba7.js +++ b/assets/js/b94a1068.29fbf944.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6628],{17723:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var t=a(58168),i=(a(96540),a(15680));a(67443);const r={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance-interfaces"},l=void 0,s={unversionedId:"inheritance-interfaces",id:"version-4.0/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-4.0/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/4.0/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/inheritance-interfaces.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance-interfaces"},sidebar:"version-4.0/docs",previous:{title:"Input types",permalink:"/docs/4.0/input-types"},next:{title:"Error handling",permalink:"/docs/4.0/error-handling"}},p={},c=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],o={toc:c},g="wrapper";function d(e){let{components:n,...a}=e;return(0,i.yg)(g,(0,t.A)({},o,a,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,i.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,i.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,i.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will translate in GraphQL schema as:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,i.yg)("p",null,"Please note that you do not need to put the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,i.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,i.yg)("p",null,"You don't have to explicitly put a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")),(0,i.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,i.yg)("p",null,"In the example above, because the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,i.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6628],{17723:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var t=a(58168),i=(a(96540),a(15680));a(67443);const r={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance-interfaces"},l=void 0,s={unversionedId:"inheritance-interfaces",id:"version-4.0/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-4.0/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/4.0/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/inheritance-interfaces.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces",original_id:"inheritance-interfaces"},sidebar:"version-4.0/docs",previous:{title:"Input types",permalink:"/docs/4.0/input-types"},next:{title:"Error handling",permalink:"/docs/4.0/error-handling"}},p={},c=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],o={toc:c},g="wrapper";function d(e){let{components:n,...a}=e;return(0,i.yg)(g,(0,t.A)({},o,a,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,i.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,i.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,i.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will translate in GraphQL schema as:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,i.yg)("p",null,"Please note that you do not need to put the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,i.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,i.yg)("p",null,"You don't have to explicitly put a ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")),(0,i.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,i.yg)("p",null,"In the example above, because the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,i.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,i.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b9d6d6e5.23ad4e90.js b/assets/js/b9d6d6e5.c8d10d7f.js similarity index 97% rename from assets/js/b9d6d6e5.23ad4e90.js rename to assets/js/b9d6d6e5.c8d10d7f.js index 7a9e079f34..39792c2d58 100644 --- a/assets/js/b9d6d6e5.23ad4e90.js +++ b/assets/js/b9d6d6e5.c8d10d7f.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8542],{72906:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>p,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating",original_id:"migrating"},r=void 0,l={unversionedId:"migrating",id:"version-4.0/migrating",title:"Migrating",description:"Migrating from v3.0 to v4.0",source:"@site/versioned_docs/version-4.0/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/4.0/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/migrating.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating",original_id:"migrating"},sidebar:"version-4.0/docs",previous:{title:"Troubleshooting",permalink:"/docs/4.0/troubleshooting"},next:{title:"Annotations reference",permalink:"/docs/4.0/annotations_reference"}},s={},g=[{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},u="wrapper";function p(e){let{components:t,...a}=e;return(0,i.yg)(u,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/annotations_reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8542],{72906:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>p,frontMatter:()=>o,metadata:()=>l,toc:()=>g});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"migrating",title:"Migrating",sidebar_label:"Migrating",original_id:"migrating"},r=void 0,l={unversionedId:"migrating",id:"version-4.0/migrating",title:"Migrating",description:"Migrating from v3.0 to v4.0",source:"@site/versioned_docs/version-4.0/migrating.md",sourceDirName:".",slug:"/migrating",permalink:"/docs/4.0/migrating",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/migrating.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"migrating",title:"Migrating",sidebar_label:"Migrating",original_id:"migrating"},sidebar:"version-4.0/docs",previous:{title:"Troubleshooting",permalink:"/docs/4.0/troubleshooting"},next:{title:"Annotations reference",permalink:"/docs/4.0/annotations_reference"}},s={},g=[{value:"Migrating from v3.0 to v4.0",id:"migrating-from-v30-to-v40",level:2}],d={toc:g},u="wrapper";function p(e){let{components:t,...a}=e;return(0,i.yg)(u,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"migrating-from-v30-to-v40"},"Migrating from v3.0 to v4.0"),(0,i.yg)("p",null,'If you are a "regular" GraphQLite user, migration to v4 should be straightforward:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Annotations are mostly untouched. The only annotation that is changed is the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation.",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Check your code for every places where you use the ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField")," annotation:"),(0,i.yg)("li",{parentName:"ul"},'The "id" attribute has been remove (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(id=true)"),"). Instead, use ",(0,i.yg)("inlineCode",{parentName:"li"},'@SourceField(outputType="ID")')),(0,i.yg)("li",{parentName:"ul"},'The "logged", "right" and "failWith" attributes have been removed (',(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(logged=true)"),").\nInstead, use the annotations attribute with the same annotations you use for the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation:\n",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField(annotations={@Logged, @FailWith(null)})")),(0,i.yg)("li",{parentName:"ul"},"If you use magic property and were creating a getter for every magic property (to put a ",(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotation on it),\nyou can now replace this getter with a ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," annotation."))),(0,i.yg)("li",{parentName:"ul"},"In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.\nIn GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it\n(this way, the schema is the same for all users). If you want the old mode, use the new\n",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/annotations_reference#hideifunauthorized-annotation"},(0,i.yg)("inlineCode",{parentName:"a"},"@HideIfUnauthorized")," annotation")),(0,i.yg)("li",{parentName:"ul"},"If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.\nThese package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do."),(0,i.yg)("li",{parentName:"ul"},"If you are relying on the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," to bootstrap GraphQLite, you have nothing to do.")),(0,i.yg)("p",null,"On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the\n",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),') or if you implemented custom "TypeMappers", you will need to adapt your code:'),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," is gone. Directly instantiate ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," in v4."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper")," class has no more constructor arguments. Use the ",(0,i.yg)("inlineCode",{parentName:"li"},"addTypeMapper")," method to register\ntype mappers in it."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," now accept an extra argument: the ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapper")," that you need to instantiate accordingly. Take\na look at the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory")," class for an example of proper configuration."),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"HydratorInterface")," and all implementations are gone. When returning an input object from a TypeMapper, the object\nmust now implement the ",(0,i.yg)("inlineCode",{parentName:"li"},"ResolvableMutableInputInterface")," (an input object type that contains its own resolver)")),(0,i.yg)("p",null,"Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager\nto bootstrap GraphQLite. Wiring directly GraphQLite classes (like the ",(0,i.yg)("inlineCode",{parentName:"p"},"FieldsBuilder"),") into your container is not recommended,\nas the signature of the constructor of those classes may vary from one minor release to another.\nUse the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaManager")," instead."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b9ea999a.a6235f86.js b/assets/js/b9ea999a.708e79ea.js similarity index 98% rename from assets/js/b9ea999a.a6235f86.js rename to assets/js/b9ea999a.708e79ea.js index 0505844efc..b523093b9a 100644 --- a/assets/js/b9ea999a.a6235f86.js +++ b/assets/js/b9ea999a.708e79ea.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2830],{16295:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>g});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations_reference",title:"Annotations reference",sidebar_label:"Annotations reference",original_id:"annotations_reference"},i=void 0,o={unversionedId:"annotations_reference",id:"version-4.1/annotations_reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-4.1/annotations_reference.md",sourceDirName:".",slug:"/annotations_reference",permalink:"/docs/4.1/annotations_reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/annotations_reference.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"annotations_reference",title:"Annotations reference",sidebar_label:"Annotations reference",original_id:"annotations_reference"},sidebar:"version-4.1/docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/4.1/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/4.1/semver"}},p={},g=[{value:"@Query annotation",id:"query-annotation",level:2},{value:"@Mutation annotation",id:"mutation-annotation",level:2},{value:"@Type annotation",id:"type-annotation",level:2},{value:"@ExtendType annotation",id:"extendtype-annotation",level:2},{value:"@Field annotation",id:"field-annotation",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"@Logged annotation",id:"logged-annotation",level:2},{value:"@Right annotation",id:"right-annotation",level:2},{value:"@FailWith annotation",id:"failwith-annotation",level:2},{value:"@HideIfUnauthorized annotation",id:"hideifunauthorized-annotation",level:2},{value:"@InjectUser annotation",id:"injectuser-annotation",level:2},{value:"@Security annotation",id:"security-annotation",level:2},{value:"@Factory annotation",id:"factory-annotation",level:2},{value:"@UseInputType annotation",id:"useinputtype-annotation",level:2},{value:"@Decorate annotation",id:"decorate-annotation",level:2},{value:"@Autowire annotation",id:"autowire-annotation",level:2},{value:"@HideParameter annotation",id:"hideparameter-annotation",level:2},{value:"@Validate annotation",id:"validate-annotation",level:2},{value:"@Assertion annotation",id:"assertion-annotation",level:2},{value:"@EnumType annotation",id:"enumtype-annotation",level:2}],y={toc:g},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},y,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query-annotation"},"@Query annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation-annotation"},"@Mutation annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type-annotation"},"@Type annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, ',(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/external_type_declaration"},"the class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/external_type_declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype-annotation"},"@ExtendType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/extend_type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"field-annotation"},"@Field annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"sourcefield-annotation"},"@SourceField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield-annotation"},"@MagicField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged-annotation"},"@Logged annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right-annotation"},"@Right annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith-annotation"},"@FailWith annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized-annotation"},"@HideIfUnauthorized annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser-annotation"},"@InjectUser annotation"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security-annotation"},"@Security annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory-annotation"},"@Factory annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"http://localhost:3000/docs/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype-annotation"},"@UseInputType annotation"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate-annotation"},"@Decorate annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/extend_input_type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire-annotation"},"@Autowire annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter-annotation"},"@HideParameter annotation"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate-annotation"},"@Validate annotation"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion-annotation"},"@Assertion annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype-annotation"},"@EnumType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2830],{16295:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>g,contentTitle:()=>i,default:()=>m,frontMatter:()=>r,metadata:()=>o,toc:()=>p});var n=a(58168),l=(a(96540),a(15680));a(67443);const r={id:"annotations_reference",title:"Annotations reference",sidebar_label:"Annotations reference",original_id:"annotations_reference"},i=void 0,o={unversionedId:"annotations_reference",id:"version-4.1/annotations_reference",title:"Annotations reference",description:"Note: all annotations are available both in a Doctrine annotation format (@Query) and in PHP 8 attribute format (#[Query]).",source:"@site/versioned_docs/version-4.1/annotations_reference.md",sourceDirName:".",slug:"/annotations_reference",permalink:"/docs/4.1/annotations_reference",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/annotations_reference.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"annotations_reference",title:"Annotations reference",sidebar_label:"Annotations reference",original_id:"annotations_reference"},sidebar:"version-4.1/docs",previous:{title:"Annotations VS Attributes",permalink:"/docs/4.1/doctrine-annotations-attributes"},next:{title:"Semantic versioning",permalink:"/docs/4.1/semver"}},g={},p=[{value:"@Query annotation",id:"query-annotation",level:2},{value:"@Mutation annotation",id:"mutation-annotation",level:2},{value:"@Type annotation",id:"type-annotation",level:2},{value:"@ExtendType annotation",id:"extendtype-annotation",level:2},{value:"@Field annotation",id:"field-annotation",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"@Logged annotation",id:"logged-annotation",level:2},{value:"@Right annotation",id:"right-annotation",level:2},{value:"@FailWith annotation",id:"failwith-annotation",level:2},{value:"@HideIfUnauthorized annotation",id:"hideifunauthorized-annotation",level:2},{value:"@InjectUser annotation",id:"injectuser-annotation",level:2},{value:"@Security annotation",id:"security-annotation",level:2},{value:"@Factory annotation",id:"factory-annotation",level:2},{value:"@UseInputType annotation",id:"useinputtype-annotation",level:2},{value:"@Decorate annotation",id:"decorate-annotation",level:2},{value:"@Autowire annotation",id:"autowire-annotation",level:2},{value:"@HideParameter annotation",id:"hideparameter-annotation",level:2},{value:"@Validate annotation",id:"validate-annotation",level:2},{value:"@Assertion annotation",id:"assertion-annotation",level:2},{value:"@EnumType annotation",id:"enumtype-annotation",level:2}],y={toc:p},d="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(d,(0,n.A)({},y,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"Note: all annotations are available both in a Doctrine annotation format (",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),") and in PHP 8 attribute format (",(0,l.yg)("inlineCode",{parentName:"p"},"#[Query]"),").\nSee ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/doctrine-annotations-attributes"},"Doctrine annotations vs PHP 8 attributes")," for more details."),(0,l.yg)("h2",{id:"query-annotation"},"@Query annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query")," annotation is used to declare a GraphQL query."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the query. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"mutation-annotation"},"@Mutation annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation is used to declare a GraphQL mutation."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": controller methods."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the mutation. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"type-annotation"},"@Type annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation is used to declare a GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, ',(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/external_type_declaration"},"the class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@Type")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed')),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Defaults to ",(0,l.yg)("em",{parentName:"td"},"true"),". Whether the targeted PHP class should be mapped by default to this type.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"external"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"Whether this is an ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/external_type_declaration"},"external type declaration"),' or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.')))),(0,l.yg)("h2",{id:"extendtype-annotation"},"@ExtendType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation is used to add fields to an existing GraphQL object type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"class"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted class. ",(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/extend_type"},"The class annotated with ",(0,l.yg)("inlineCode",{parentName:"a"},"@ExtendType")," is a service"),".")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},"see below"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The targeted GraphQL output type.")))),(0,l.yg)("p",null,'One and only one of "class" and "name" parameter can be passed at the same time.'),(0,l.yg)("h2",{id:"field-annotation"},"@Field annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods of classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field. If skipped, the name of the method is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of a query.")))),(0,l.yg)("h2",{id:"sourcefield-annotation"},"@SourceField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation is used to declare a GraphQL field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Forces the GraphQL output type of the field. Otherwise, return type is used.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #SourceField PHP 8 attribute)')))),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive."),(0,l.yg)("h2",{id:"magicfield-annotation"},"@MagicField annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation is used to declare a GraphQL field that originates from a PHP magic property (using ",(0,l.yg)("inlineCode",{parentName:"p"},"__get")," magic method)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("a",{parentName:"td",href:"/docs/4.1/custom-types"},"outputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL output type of the field.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"phpType"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no"),"(*)"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The PHP type of the field (as you would write it in a Docblock)")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"annotations"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"array\\"),(0,l.yg)("td",{parentName:"tr",align:null},'A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here. Available in Doctrine annotations only (not available in the #MagicField PHP 8 attribute)')))),(0,l.yg)("p",null,"(*) ",(0,l.yg)("strong",{parentName:"p"},"Note"),": ",(0,l.yg)("inlineCode",{parentName:"p"},"outputType")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"phpType")," are mutually exclusive. You MUST provide one of them."),(0,l.yg)("h2",{id:"logged-annotation"},"@Logged annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," annotation is used to declare a Query/Mutation/Field is only visible to logged users."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("p",null,"This annotation allows no attributes."),(0,l.yg)("h2",{id:"right-annotation"},"@Right annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the right.")))),(0,l.yg)("h2",{id:"failwith-annotation"},"@FailWith annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation is used to declare a default value to return in the user is not authorized to see a specific\nquery / mutation / field (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"value"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"mixed"),(0,l.yg)("td",{parentName:"tr",align:null},"The value to return if the user is not authorized.")))),(0,l.yg)("h2",{id:"hideifunauthorized-annotation"},"@HideIfUnauthorized annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation is used to completely hide the query / mutation / field if the user is not authorized\nto access it (according to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations)."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," and one of ",(0,l.yg)("inlineCode",{parentName:"p"},"@Logged")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Right")," annotations."),(0,l.yg)("p",null,(0,l.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"@FailWith")," are mutually exclusive."),(0,l.yg)("h2",{id:"injectuser-annotation"},"@InjectUser annotation"),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to inject an instance of the current user logged in into a parameter of your\nquery / mutation / field."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")))),(0,l.yg)("h2",{id:"security-annotation"},"@Security annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Security")," annotation can be used to check fin-grained access rights.\nIt is very flexible: it allows you to pass an expression that can contains custom logic."),(0,l.yg)("p",null,"See ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/fine-grained-security"},"the fine grained security page")," for more details."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"default")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The security expression")))),(0,l.yg)("h2",{id:"factory-annotation"},"@Factory annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," annotation is used to declare a factory that turns GraphQL input types into objects."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the input type. If skipped, the name of class returned by the factory is used instead.")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"default"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"bool"),(0,l.yg)("td",{parentName:"tr",align:null},"If ",(0,l.yg)("inlineCode",{parentName:"td"},"true"),", this factory will be used by default for its PHP return type. If set to ",(0,l.yg)("inlineCode",{parentName:"td"},"false"),", you must explicitly ",(0,l.yg)("a",{parentName:"td",href:"http://localhost:3000/docs/input-types#declaring-several-input-types-for-the-same-php-class"},"reference this factory using the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Parameter")," annotation"),".")))),(0,l.yg)("h2",{id:"useinputtype-annotation"},"@UseInputType annotation"),(0,l.yg)("p",null,"Used to override the GraphQL input type of a PHP parameter."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"inputType")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type to force for this input field")))),(0,l.yg)("h2",{id:"decorate-annotation"},"@Decorate annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorate")," annotation is used ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/extend_input_type"},"to extend/modify/decorate an input type declared with the ",(0,l.yg)("inlineCode",{parentName:"a"},"@Factory")," annotation"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),': methods from classes in the "types" namespace.'),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The GraphQL input type name extended by this decorator.")))),(0,l.yg)("h2",{id:"autowire-annotation"},"@Autowire annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/autowiring"},"Resolves a PHP parameter from the container"),"."),(0,l.yg)("p",null,"Useful to inject services directly into ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," method arguments."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"identifier")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},'The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.')))),(0,l.yg)("h2",{id:"hideparameter-annotation"},"@HideParameter annotation"),(0,l.yg)("p",null,"Removes ",(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/input-types#ignoring-some-parameters"},"an argument from the GraphQL schema"),"."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter to hide")))),(0,l.yg)("h2",{id:"validate-annotation"},"@Validate annotation"),(0,l.yg)("div",{class:"alert alert--info"},"This annotation is only available in the GraphQLite Laravel package"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/laravel-package-advanced"},"Validates a user input in Laravel"),"."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"rule")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"Laravel validation rules")))),(0,l.yg)("p",null,"Sample:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre"},'@Validate(for="$email", rule="email|unique:users")\n')),(0,l.yg)("h2",{id:"assertion-annotation"},"@Assertion annotation"),(0,l.yg)("p",null,(0,l.yg)("a",{parentName:"p",href:"/docs/4.1/validation"},"Validates a user input"),"."),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation is available in the ",(0,l.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," third party package.\nIt is available out of the box if you use the Symfony bundle."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": methods annotated with ",(0,l.yg)("inlineCode",{parentName:"p"},"@Query"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Mutation"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Field"),", ",(0,l.yg)("inlineCode",{parentName:"p"},"@Factory")," or ",(0,l.yg)("inlineCode",{parentName:"p"},"@Decorator")," annotation."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"for")),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"yes")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the PHP parameter")),(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"constraint")),(0,l.yg)("td",{parentName:"tr",align:null},"*yes"),(0,l.yg)("td",{parentName:"tr",align:null},"annotation"),(0,l.yg)("td",{parentName:"tr",align:null},"One (or many) Symfony validation annotations.")))),(0,l.yg)("h2",{id:"enumtype-annotation"},"@EnumType annotation"),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"@EnumType"),' annotation is used to change the name of a "Enum" type.\nNote that if you do not want to change the name, the annotation is optionnal. Any object extending ',(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum"),"\nis automatically mapped to a GraphQL enum type."),(0,l.yg)("p",null,(0,l.yg)("strong",{parentName:"p"},"Applies on"),": classes extending the ",(0,l.yg)("inlineCode",{parentName:"p"},"MyCLabs\\Enum\\Enum")," base class."),(0,l.yg)("table",null,(0,l.yg)("thead",{parentName:"table"},(0,l.yg)("tr",{parentName:"thead"},(0,l.yg)("th",{parentName:"tr",align:null},"Attribute"),(0,l.yg)("th",{parentName:"tr",align:null},"Compulsory"),(0,l.yg)("th",{parentName:"tr",align:null},"Type"),(0,l.yg)("th",{parentName:"tr",align:null},"Definition"))),(0,l.yg)("tbody",{parentName:"table"},(0,l.yg)("tr",{parentName:"tbody"},(0,l.yg)("td",{parentName:"tr",align:null},"name"),(0,l.yg)("td",{parentName:"tr",align:null},(0,l.yg)("em",{parentName:"td"},"no")),(0,l.yg)("td",{parentName:"tr",align:null},"string"),(0,l.yg)("td",{parentName:"tr",align:null},"The name of the enum type (in the GraphQL schema)")))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ba7653ad.e90cc1de.js b/assets/js/ba7653ad.982c6c9b.js similarity index 89% rename from assets/js/ba7653ad.e90cc1de.js rename to assets/js/ba7653ad.982c6c9b.js index ba5aec6df1..b7f07d5a73 100644 --- a/assets/js/ba7653ad.e90cc1de.js +++ b/assets/js/ba7653ad.982c6c9b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4716],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),u=a(56347),i=a(57485),s=a(31682),p=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function f(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=c(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,s]=h({queryString:a,groupId:n}),[d,f]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),g=(()=>{const e=i??d;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{g&&u(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),s(e),f(e)}),[s,f,l]),tabValues:l}}var g=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:u,selectValue:i,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==u&&(d(t),i(n))},m=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:c},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":u===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},14912:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>s,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const u={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},i=void 0,s={unversionedId:"file-uploads",id:"version-4.3/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-4.3/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/4.3/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/file-uploads.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"version-4.3/docs",previous:{title:"Prefetching records",permalink:"/docs/4.3/prefetch-method"},next:{title:"Pagination",permalink:"/docs/4.3/pagination"}},p={},d=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],c={toc:d},m="wrapper";function h(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,r.yg)("p",null,"You must start by installing this package:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,r.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,r.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,r.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,r.yg)("p",null,"In order to use this, you must first be sure that the ",(0,r.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,r.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,r.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,r.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")))),(0,r.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,r.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4716],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),u=a(56347),i=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function f(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,s]=h({queryString:a,groupId:n}),[c,f]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),g=(()=>{const e=i??c;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{g&&u(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),s(e),f(e)}),[s,f,l]),tabValues:l}}var g=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:u,selectValue:i,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==u&&(c(t),i(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":u===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},14912:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const u={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},i=void 0,s={unversionedId:"file-uploads",id:"version-4.3/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-4.3/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/4.3/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/file-uploads.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"version-4.3/docs",previous:{title:"Prefetching records",permalink:"/docs/4.3/prefetch-method"},next:{title:"Pagination",permalink:"/docs/4.3/pagination"}},p={},c=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],d={toc:c},m="wrapper";function h(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,r.yg)("p",null,"You must start by installing this package:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,r.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,r.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,r.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,r.yg)("p",null,"In order to use this, you must first be sure that the ",(0,r.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,r.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,r.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,r.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")))),(0,r.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,r.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/bb5ef1b7.82e72688.js b/assets/js/bb5ef1b7.80062520.js similarity index 98% rename from assets/js/bb5ef1b7.82e72688.js rename to assets/js/bb5ef1b7.80062520.js index 644c1df560..e38f601fd7 100644 --- a/assets/js/bb5ef1b7.82e72688.js +++ b/assets/js/bb5ef1b7.80062520.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[651],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),i=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(n.location.search);t.set(i,e),n.replace({...n.location,search:t.toString()})}),[i,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,i]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:n}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,t)),r.createElement(b,(0,n.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},35575:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),i=(a(67443),a(11470)),o=a(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-6.0/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-6.0/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/6.0/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/autowiring.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"docs",previous:{title:"Type mapping",permalink:"/docs/6.0/type-mapping"},next:{title:"Extending a type",permalink:"/docs/6.0/extend-type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(h,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[651],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const i={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(i.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),i=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function h(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(i),(0,r.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(n.location.search);t.set(i,e),n.replace({...n.location,search:t.toString()})}),[i,n])]}function g(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,i=p(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:i}))),[s,u]=m({queryString:a,groupId:n}),[d,g]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,i]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&i.set(e)}),[a,i])]}({groupId:n}),y=(()=>{const e=s??d;return h({value:e,tabValues:i})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),g(e)}),[u,g,i]),tabValues:i}}var y=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==l&&(d(t),s(n))},h=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:p},o,{className:(0,i.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const i=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=g(e);return r.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},r.createElement(v,(0,n.A)({},e,t)),r.createElement(b,(0,n.A)({},e,t)))}function T(e){const t=(0,y.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},35575:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),i=(a(67443),a(11470)),o=a(19365);const l={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},s=void 0,u={unversionedId:"autowiring",id:"version-6.0/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-6.0/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/6.0/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/autowiring.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services"},sidebar:"docs",previous:{title:"Type mapping",permalink:"/docs/6.0/type-mapping"},next:{title:"Extending a type",permalink:"/docs/6.0/extend-type"}},c={},d=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],p={toc:d},h="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(h,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,r.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,r.yg)("p",null,"Most of the time, your ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,r.yg)("h2",{id:"sample"},"Sample"),(0,r.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(\n #[Autowire]\n TranslatorInterface $translator\n ): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")))),(0,r.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,r.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,r.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,r.yg)("h2",{id:"best-practices"},"Best practices"),(0,r.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,r.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] MyTranslator $translator): string"))),(0,r.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,r.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,r.yg)("pre",null,(0,r.yg)("code",null,"#[Field] public function getName(#[Autowire] TranslatorInterface $translator): string"))),(0,r.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,r.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,r.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,r.yg)(i.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Autowire(identifier: "translator")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')))),(0,r.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,r.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,r.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,r.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,r.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,r.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,r.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,r.yg)("a",{parentName:"p",href:"extend-type"},"how to extend a type"),"."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/bb9fe7c3.6af7855f.js b/assets/js/bb9fe7c3.6dd5e99b.js similarity index 99% rename from assets/js/bb9fe7c3.6af7855f.js rename to assets/js/bb9fe7c3.6dd5e99b.js index 597c61543e..0f4719e5f3 100644 --- a/assets/js/bb9fe7c3.6af7855f.js +++ b/assets/js/bb9fe7c3.6dd5e99b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5832],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),i=a(20053);const o={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,i.A)(o.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>N});var n=a(58168),i=a(96540),o=a(20053),r=a(23104),l=a(56347),s=a(57485),u=a(31682),d=a(89466);function p(e){return function(e){return i.Children.map(e,(e=>{if(!e||(0,i.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:i}}=e;return{value:t,label:a,attributes:n,default:i}}))}function c(e){const{values:t,children:a}=e;return(0,i.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,i.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function m(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=c(e),[r,l]=(0,i.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,u]=h({queryString:a,groupId:n}),[p,m]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,d.Dv)(a);return[n,(0,i.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),g=(()=>{const e=s??p;return y({value:e,tabValues:o})?e:null})();(0,i.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:r,selectValue:(0,i.useCallback)((e=>{if(!y({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),m(e)}),[u,m,o]),tabValues:o}}var g=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=d.indexOf(t),n=u[a].value;n!==l&&(p(t),s(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=d.indexOf(e.currentTarget)+1;t=d[a]??d[0];break}case"ArrowLeft":{const a=d.indexOf(e.currentTarget)-1;t=d[a]??d[d.length-1];break}}t?.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:r}=e;return i.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>d.push(e),onKeyDown:y,onClick:c},r,{className:(0,o.A)("tabs__item",v.tabItem,r?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,i.cloneElement)(e,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,i.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=m(e);return i.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},i.createElement(f,(0,n.A)({},e,t)),i.createElement(b,(0,n.A)({},e,t)))}function N(e){const t=(0,g.A)();return i.createElement(w,(0,n.A)({key:String(t)},e))}},71650:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var n=a(58168),i=(a(96540),a(15680));a(67443),a(11470),a(19365);const o={id:"validation",title:"Validation",sidebar_label:"User input validation"},r=void 0,l={unversionedId:"validation",id:"version-6.1/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-6.1/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/6.1/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/validation.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"docs",previous:{title:"Error handling",permalink:"/docs/6.1/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/6.1/authentication-authorization"}},s={},u=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3},{value:"Custom InputType Validation",id:"custom-inputtype-validation",level:2}],d={toc:u},p="wrapper";function c(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,i.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,i.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,i.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,i.yg)("a",{parentName:"p",href:"/docs/6.1/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,i.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,i.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,i.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,i.yg)("pre",{parentName:"li"},(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,i.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,i.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,i.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")),(0,i.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')),(0,i.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,i.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,i.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,i.yg)("p",null,"If the data entered by the user is ",(0,i.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,i.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\GraphQLite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,i.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,i.yg)("p",null,"You can also pass an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,i.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"),(0,i.yg)("h2",{id:"custom-inputtype-validation"},"Custom InputType Validation"),(0,i.yg)("p",null,"GraphQLite also supports a fully custom validation implementation for all input types defined with an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation or PHP8 ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute. This offers a way to validate input types before they're available as a method parameter of your query and mutation controllers. This way, when you're using your query or mutation controllers, you can feel confident that your input type objects have already been validated."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("p",null,"It's important to note that this validation implementation does not validate input types created with a factory. If you are creating an input type with a factory, or using primitive parameters in your query/mutation controllers, you should be sure to validate these independently. This is strictly for input type objects."),(0,i.yg)("p",null,"You can use one of the framework validation libraries listed above or implement your own validation for these cases. If you're using input type objects for most all of your query and mutation controllers, then there is little additional validation concerns with regards to user input. There are many reasons why you should consider defaulting to an InputType object, as opposed to individual arguments, for your queries and mutations. This is just one additional perk.")),(0,i.yg)("p",null,"To get started with validation on input types defined by an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation, you'll first need to register your validator with the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$factory = new SchemaFactory($cache, $this->container);\n$factory->addControllerNamespace('App\\\\Controllers');\n$factory->addTypeNamespace('App');\n// Register your validator\n$factory->setInputTypeValidator($this->container->get('your_validator'));\n$factory->createSchema();\n")),(0,i.yg)("p",null,"Your input type validator must implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Types\\InputTypeValidatorInterface"),", as shown below:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"interface InputTypeValidatorInterface\n{\n /**\n * Checks to see if the Validator is currently enabled.\n */\n public function isEnabled(): bool;\n\n /**\n * Performs the validation of the InputType.\n *\n * @param object $input The input type object to validate\n */\n public function validate(object $input): void;\n}\n")),(0,i.yg)("p",null,"The interface is quite simple. Handle all of your own validation logic in the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method. For example, you might use Symfony's annotation based validation in addition to some other custom validation logic. It's really up to you on how you wish to handle your own validation. The ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method will receive the input type object populated with the user input."),(0,i.yg)("p",null,"You'll notice that the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method has a ",(0,i.yg)("inlineCode",{parentName:"p"},"void")," return. The purpose here is to encourage you to throw an Exception or handle validation output however you best see fit. GraphQLite does it's best to stay out of your way and doesn't make attempts to handle validation output. You can, however, throw an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")," as usual (see ",(0,i.yg)("a",{parentName:"p",href:"error-handling"},"Error Handling")," for more details)."),(0,i.yg)("p",null,"Also available is the ",(0,i.yg)("inlineCode",{parentName:"p"},"isEnabled")," method. This method is checked before executing validation on an InputType being resolved. You can work out your own logic to selectively enable or disable validation through this method. In most cases, you can simply return ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," to keep it always enabled."),(0,i.yg)("p",null,"And that's it, now, anytime an input type is resolved, the validator will be executed on that input type immediately after it has been hydrated with user input."))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5832],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),i=a(20053);const o={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,i.A)(o.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>N});var n=a(58168),i=a(96540),o=a(20053),r=a(23104),l=a(56347),s=a(57485),u=a(31682),d=a(89466);function p(e){return function(e){return i.Children.map(e,(e=>{if(!e||(0,i.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:i}}=e;return{value:t,label:a,attributes:n,default:i}}))}function c(e){const{values:t,children:a}=e;return(0,i.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,i.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function m(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=c(e),[r,l]=(0,i.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,u]=h({queryString:a,groupId:n}),[p,m]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,d.Dv)(a);return[n,(0,i.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),g=(()=>{const e=s??p;return y({value:e,tabValues:o})?e:null})();(0,i.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:r,selectValue:(0,i.useCallback)((e=>{if(!y({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),m(e)}),[u,m,o]),tabValues:o}}var g=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const d=[],{blockElementScrollPositionUntilNextRender:p}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=d.indexOf(t),n=u[a].value;n!==l&&(p(t),s(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=d.indexOf(e.currentTarget)+1;t=d[a]??d[0];break}case"ArrowLeft":{const a=d.indexOf(e.currentTarget)-1;t=d[a]??d[d.length-1];break}}t?.focus()};return i.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:r}=e;return i.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>d.push(e),onKeyDown:y,onClick:c},r,{className:(0,o.A)("tabs__item",v.tabItem,r?.className,{"tabs__item--active":l===t})}),a??t)})))}function b(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,i.cloneElement)(e,{className:"margin-top--md"}):null}return i.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,i.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=m(e);return i.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},i.createElement(f,(0,n.A)({},e,t)),i.createElement(b,(0,n.A)({},e,t)))}function N(e){const t=(0,g.A)();return i.createElement(w,(0,n.A)({key:String(t)},e))}},71650:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var n=a(58168),i=(a(96540),a(15680));a(67443),a(11470),a(19365);const o={id:"validation",title:"Validation",sidebar_label:"User input validation"},r=void 0,l={unversionedId:"validation",id:"version-6.1/validation",title:"Validation",description:"GraphQLite does not handle user input validation by itself. It is out of its scope.",source:"@site/versioned_docs/version-6.1/validation.mdx",sourceDirName:".",slug:"/validation",permalink:"/docs/6.1/validation",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/validation.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"validation",title:"Validation",sidebar_label:"User input validation"},sidebar:"docs",previous:{title:"Error handling",permalink:"/docs/6.1/error-handling"},next:{title:"Authentication and authorization",permalink:"/docs/6.1/authentication-authorization"}},s={},u=[{value:"Validating user input with Laravel",id:"validating-user-input-with-laravel",level:2},{value:"Validating user input with Symfony validator",id:"validating-user-input-with-symfony-validator",level:2},{value:"Using the Symfony validator bridge",id:"using-the-symfony-validator-bridge",level:3},{value:"Using the validator directly on a query / mutation / factory ...",id:"using-the-validator-directly-on-a-query--mutation--factory-",level:3},{value:"Custom InputType Validation",id:"custom-inputtype-validation",level:2}],d={toc:u},p="wrapper";function c(e){let{components:t,...a}=e;return(0,i.yg)(p,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite does not handle user input validation by itself. It is out of its scope."),(0,i.yg)("p",null,"However, it can integrate with your favorite framework validation mechanism. The way you validate user input will\ntherefore depend on the framework you are using."),(0,i.yg)("h2",{id:"validating-user-input-with-laravel"},"Validating user input with Laravel"),(0,i.yg)("p",null,"If you are using Laravel, jump directly to the ",(0,i.yg)("a",{parentName:"p",href:"/docs/6.1/laravel-package-advanced#support-for-laravel-validation-rules"},"GraphQLite Laravel package advanced documentation"),"\nto learn how to use the Laravel validation with GraphQLite."),(0,i.yg)("h2",{id:"validating-user-input-with-symfony-validator"},"Validating user input with Symfony validator"),(0,i.yg)("p",null,"GraphQLite provides a bridge to use the ",(0,i.yg)("a",{parentName:"p",href:"https://symfony.com/doc/current/validation.html"},"Symfony validator")," directly in your application."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},"If you are using Symfony and the Symfony GraphQLite bundle, the bridge is available out of the box")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("p",{parentName:"li"},'If you are using another framework, the "Symfony validator" component can be used in standalone mode. If you want to\nadd it to your project, you can require the ',(0,i.yg)("em",{parentName:"p"},"thecodingmachine/graphqlite-symfony-validator-bridge")," package:"),(0,i.yg)("pre",{parentName:"li"},(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require thecodingmachine/graphqlite-symfony-validator-bridge\n")))),(0,i.yg)("h3",{id:"using-the-symfony-validator-bridge"},"Using the Symfony validator bridge"),(0,i.yg)("p",null,"Usually, when you use the Symfony validator component, you put annotations in your entities and you validate those entities\nusing the ",(0,i.yg)("inlineCode",{parentName:"p"},"Validator")," object."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="UserController.php"',title:'"UserController.php"'},"use Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse TheCodingMachine\\GraphQLite\\Validator\\ValidationFailedException\n\nclass UserController\n{\n private $validator;\n\n public function __construct(ValidatorInterface $validator)\n {\n $this->validator = $validator;\n }\n\n #[Mutation]\n public function createUser(string $email, string $password): User\n {\n $user = new User($email, $password);\n\n // Let's validate the user\n $errors = $this->validator->validate($user);\n\n // Throw an appropriate GraphQL exception if validation errors are encountered\n ValidationFailedException::throwException($errors);\n\n // No errors? Let's continue and save the user\n // ...\n }\n}\n")),(0,i.yg)("p",null,"Validation rules are added directly to the object in the domain model:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="User.php"',title:'"User.php"'},'use Symfony\\Component\\Validator\\Constraints as Assert;\n\nclass User\n{\n #[Assert\\Email(message: "The email \'{{ value }}\' is not a valid email.", checkMX: true)]\n private $email;\n\n /**\n * The NotCompromisedPassword assertion asks the "HaveIBeenPawned" service if your password has already leaked or not.\n */\n #[Assert\\NotCompromisedPassword]\n private $password;\n\n public function __construct(string $email, string $password)\n {\n $this->email = $email;\n $this->password = $password;\n }\n\n // ...\n}\n')),(0,i.yg)("p",null,'If a validation fails, GraphQLite will return the failed validations in the "errors" section of the JSON response:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email \'\\"foo@thisdomaindoesnotexistatall.com\\"\' is not a valid email.",\n "extensions": {\n "code": "bf447c1c-0266-4e10-9c6c-573df282e413",\n "field": "email",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,i.yg)("h3",{id:"using-the-validator-directly-on-a-query--mutation--factory-"},"Using the validator directly on a query / mutation / factory ..."),(0,i.yg)("p",null,'If the data entered by the user is mapped to an object, please use the "validator" instance directly as explained in\nthe last chapter. It is a best practice to put your validation layer as close as possible to your domain model.'),(0,i.yg)("p",null,"If the data entered by the user is ",(0,i.yg)("strong",{parentName:"p"},"not")," mapped to an object, you can directly annotate your query, mutation, factory..."),(0,i.yg)("div",{class:"alert alert--warning"},"You generally don't want to do this. It is a best practice to put your validation constraints on your domain objects. Only use this technique if you want to validate user input and user input will not be stored in a domain object."),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@Assertion")," annotation to validate directly the user input."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'use Symfony\\Component\\Validator\\Constraints as Assert;\nuse TheCodingMachine\\GraphQLite\\Validator\\Annotations\\Assertion;\n\n/**\n * @Query\n * @Assertion(for="email", constraint=@Assert\\Email())\n */\npublic function findByMail(string $email): User\n{\n // ...\n}\n')),(0,i.yg)("p",null,'Notice that the "constraint" parameter contains an annotation (it is an annotation wrapped in an annotation).'),(0,i.yg)("p",null,"You can also pass an array to the ",(0,i.yg)("inlineCode",{parentName:"p"},"constraint")," parameter:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'@Assertion(for="email", constraint={@Assert\\NotBlank(), @Assert\\Email()})\n')),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Heads up!"),' The "@Assertion" annotation is only available as a ',(0,i.yg)("strong",null,"Doctrine annotations"),". You cannot use it as a PHP 8 attributes"),(0,i.yg)("h2",{id:"custom-inputtype-validation"},"Custom InputType Validation"),(0,i.yg)("p",null,"GraphQLite also supports a fully custom validation implementation for all input types defined with an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation or PHP8 ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute. This offers a way to validate input types before they're available as a method parameter of your query and mutation controllers. This way, when you're using your query or mutation controllers, you can feel confident that your input type objects have already been validated."),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("p",null,"It's important to note that this validation implementation does not validate input types created with a factory. If you are creating an input type with a factory, or using primitive parameters in your query/mutation controllers, you should be sure to validate these independently. This is strictly for input type objects."),(0,i.yg)("p",null,"You can use one of the framework validation libraries listed above or implement your own validation for these cases. If you're using input type objects for most all of your query and mutation controllers, then there is little additional validation concerns with regards to user input. There are many reasons why you should consider defaulting to an InputType object, as opposed to individual arguments, for your queries and mutations. This is just one additional perk.")),(0,i.yg)("p",null,"To get started with validation on input types defined by an ",(0,i.yg)("inlineCode",{parentName:"p"},"@Input")," annotation, you'll first need to register your validator with the ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"$factory = new SchemaFactory($cache, $this->container);\n$factory->addControllerNamespace('App\\\\Controllers');\n$factory->addTypeNamespace('App');\n// Register your validator\n$factory->setInputTypeValidator($this->container->get('your_validator'));\n$factory->createSchema();\n")),(0,i.yg)("p",null,"Your input type validator must implement the ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Types\\InputTypeValidatorInterface"),", as shown below:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"interface InputTypeValidatorInterface\n{\n /**\n * Checks to see if the Validator is currently enabled.\n */\n public function isEnabled(): bool;\n\n /**\n * Performs the validation of the InputType.\n *\n * @param object $input The input type object to validate\n */\n public function validate(object $input): void;\n}\n")),(0,i.yg)("p",null,"The interface is quite simple. Handle all of your own validation logic in the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method. For example, you might use Symfony's annotation based validation in addition to some other custom validation logic. It's really up to you on how you wish to handle your own validation. The ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method will receive the input type object populated with the user input."),(0,i.yg)("p",null,"You'll notice that the ",(0,i.yg)("inlineCode",{parentName:"p"},"validate")," method has a ",(0,i.yg)("inlineCode",{parentName:"p"},"void")," return. The purpose here is to encourage you to throw an Exception or handle validation output however you best see fit. GraphQLite does it's best to stay out of your way and doesn't make attempts to handle validation output. You can, however, throw an instance of ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")," as usual (see ",(0,i.yg)("a",{parentName:"p",href:"error-handling"},"Error Handling")," for more details)."),(0,i.yg)("p",null,"Also available is the ",(0,i.yg)("inlineCode",{parentName:"p"},"isEnabled")," method. This method is checked before executing validation on an InputType being resolved. You can work out your own logic to selectively enable or disable validation through this method. In most cases, you can simply return ",(0,i.yg)("inlineCode",{parentName:"p"},"true")," to keep it always enabled."),(0,i.yg)("p",null,"And that's it, now, anytime an input type is resolved, the validator will be executed on that input type immediately after it has been hydrated with user input."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/bcb6471f.0644f520.js b/assets/js/bcb6471f.ecbcbd7c.js similarity index 98% rename from assets/js/bcb6471f.0644f520.js rename to assets/js/bcb6471f.ecbcbd7c.js index 531ec9dd97..b2775dcefa 100644 --- a/assets/js/bcb6471f.0644f520.js +++ b/assets/js/bcb6471f.ecbcbd7c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3196],{77375:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var a=t(58168),r=(t(96540),t(15680));t(67443);const l={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},o=void 0,i={unversionedId:"symfony-bundle",id:"version-4.1/symfony-bundle",title:"Getting started with Symfony",description:"The GraphQLite bundle is compatible with Symfony 4.x and Symfony 5.x.",source:"@site/versioned_docs/version-4.1/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/4.1/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/symfony-bundle.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},sidebar:"version-4.1/docs",previous:{title:"Getting Started",permalink:"/docs/4.1/getting-started"},next:{title:"Laravel package",permalink:"/docs/4.1/laravel-package"}},p={},s=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],y={toc:s},g="wrapper";function d(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},y,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,r.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,r.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,r.yg)("p",null,"Now, go to the ",(0,r.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"config/packages/graphqlite.yaml")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,r.yg)("p",null,"More advanced parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,r.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,r.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,r.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"app/AppKernel.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>o,default:()=>d,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var a=t(58168),r=(t(96540),t(15680));t(67443);const l={id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},o=void 0,i={unversionedId:"symfony-bundle",id:"version-4.1/symfony-bundle",title:"Getting started with Symfony",description:"The GraphQLite bundle is compatible with Symfony 4.x and Symfony 5.x.",source:"@site/versioned_docs/version-4.1/symfony-bundle.md",sourceDirName:".",slug:"/symfony-bundle",permalink:"/docs/4.1/symfony-bundle",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/symfony-bundle.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle",title:"Getting started with Symfony",sidebar_label:"Symfony bundle",original_id:"symfony-bundle"},sidebar:"version-4.1/docs",previous:{title:"Getting Started",permalink:"/docs/4.1/getting-started"},next:{title:"Laravel package",permalink:"/docs/4.1/laravel-package"}},p={},s=[{value:"Applications that use Symfony Flex",id:"applications-that-use-symfony-flex",level:2},{value:"Applications that don't use Symfony Flex",id:"applications-that-dont-use-symfony-flex",level:2},{value:"Advanced configuration",id:"advanced-configuration",level:2},{value:"Customizing error handling",id:"customizing-error-handling",level:3}],y={toc:s},g="wrapper";function d(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},y,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The GraphQLite bundle is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Symfony 4.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Symfony 5.x"),"."),(0,r.yg)("h2",{id:"applications-that-use-symfony-flex"},"Applications that use Symfony Flex"),(0,r.yg)("p",null,"Open a command console, enter your project directory and execute:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,r.yg)("p",null,"Now, go to the ",(0,r.yg)("inlineCode",{parentName:"p"},"config/packages/graphqlite.yaml")," file and edit the namespaces to match your application."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"config/packages/graphqlite.yaml")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n namespace:\n # The namespace(s) that will store your GraphQLite controllers.\n # It accept either a string or a list of strings.\n controllers: App\\GraphQLController\\\n # The namespace(s) that will store your GraphQL types and factories.\n # It accept either a string or a list of strings.\n types:\n - App\\Types\\\n - App\\Entity\\\n")),(0,r.yg)("p",null,"More advanced parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"#advanced-configuration"},'"advanced configuration" section')),(0,r.yg)("h2",{id:"applications-that-dont-use-symfony-flex"},"Applications that don't use Symfony Flex"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-bundle\n")),(0,r.yg)("p",null,"Enable the library by adding it to the list of registered bundles in the ",(0,r.yg)("inlineCode",{parentName:"p"},"app/AppKernel.php")," file:"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"app/AppKernel.php")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"{p.r(a),p.d(a,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,o={unversionedId:"internals",id:"version-5.0/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-5.0/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/5.0/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/internals.md",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"version-5.0/docs",previous:{title:"Laravel specific features",permalink:"/docs/5.0/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/5.0/troubleshooting"}},l={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/5.0/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2952],{30905:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,o={unversionedId:"internals",id:"version-5.0/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-5.0/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/5.0/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/internals.md",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"version-5.0/docs",previous:{title:"Laravel specific features",permalink:"/docs/5.0/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/5.0/troubleshooting"}},l={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/5.0/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/bd2c4a98.fe752412.js b/assets/js/bd2c4a98.cc91c3c6.js similarity index 99% rename from assets/js/bd2c4a98.fe752412.js rename to assets/js/bd2c4a98.cc91c3c6.js index c7da3de163..6935c0cd4f 100644 --- a/assets/js/bd2c4a98.fe752412.js +++ b/assets/js/bd2c4a98.cc91c3c6.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9026],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var r=a(96540),t=a(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>$});var r=a(58168),t=a(96540),o=a(20053),i=a(23104),l=a(56347),s=a(57485),c=a(31682),p=a(89466);function u(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:r,default:t}}=e;return{value:n,label:a,attributes:r,default:t}}))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function h(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:a}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,c]=m({queryString:a,groupId:r}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,p.Dv)(a);return[r,(0,t.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:r}),g=(()=>{const e=s??u;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),c(e),y(e)}),[c,y,o]),tabValues:o}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:s,tabValues:c}=e;const p=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),d=e=>{const n=e.currentTarget,a=p.indexOf(n),r=c[a].value;r!==l&&(u(n),s(r))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;n=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;n=p[a]??p[p.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return t.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>p.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),a??n)})))}function w(e){let{lazy:n,children:a,selectedValue:r}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,r.A)({},e,n)),t.createElement(w,(0,r.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,r.A)({key:String(n)},e))}},30999:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>c,toc:()=>u});var r=a(58168),t=(a(96540),a(15680)),o=(a(67443),a(11470)),i=a(19365);const l={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},s=void 0,c={unversionedId:"other-frameworks",id:"version-4.3/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-4.3/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/4.3/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/other-frameworks.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"version-4.3/docs",previous:{title:"Universal service providers",permalink:"/docs/4.3/universal-service-providers"},next:{title:"Queries",permalink:"/docs/4.3/queries"}},p={},u=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],d={toc:u},h="wrapper";function m(e){let{components:n,...a}=e;return(0,t.yg)(h,(0,r.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'$builder->setUrl("/graphql"); // Modify the URL endpoint (defaults to /graphql)\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object. Use this object to configure Webonyx in details.\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n')),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,t.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n"))),(0,t.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")))),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9026],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var r=a(96540),t=a(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>$});var r=a(58168),t=a(96540),o=a(20053),i=a(23104),l=a(56347),s=a(57485),c=a(31682),p=a(89466);function u(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:r,default:t}}=e;return{value:n,label:a,attributes:r,default:t}}))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function h(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:a}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,c]=m({queryString:a,groupId:r}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,p.Dv)(a);return[r,(0,t.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:r}),g=(()=>{const e=s??u;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),c(e),y(e)}),[c,y,o]),tabValues:o}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:s,tabValues:c}=e;const p=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),d=e=>{const n=e.currentTarget,a=p.indexOf(n),r=c[a].value;r!==l&&(u(n),s(r))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;n=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;n=p[a]??p[p.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return t.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>p.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),a??n)})))}function w(e){let{lazy:n,children:a,selectedValue:r}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,r.A)({},e,n)),t.createElement(w,(0,r.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,r.A)({key:String(n)},e))}},30999:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>c,toc:()=>u});var r=a(58168),t=(a(96540),a(15680)),o=(a(67443),a(11470)),i=a(19365);const l={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},s=void 0,c={unversionedId:"other-frameworks",id:"version-4.3/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-4.3/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/4.3/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/other-frameworks.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"version-4.3/docs",previous:{title:"Universal service providers",permalink:"/docs/4.3/universal-service-providers"},next:{title:"Queries",permalink:"/docs/4.3/queries"}},p={},u=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],d={toc:u},h="wrapper";function m(e){let{components:n,...a}=e;return(0,t.yg)(h,(0,r.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'$builder->setUrl("/graphql"); // Modify the URL endpoint (defaults to /graphql)\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object. Use this object to configure Webonyx in details.\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n')),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,t.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n"))),(0,t.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")))),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/bda39da3.60aa5ceb.js b/assets/js/bda39da3.da603a75.js similarity index 98% rename from assets/js/bda39da3.60aa5ceb.js rename to assets/js/bda39da3.da603a75.js index 07983f10d7..89f819bfbb 100644 --- a/assets/js/bda39da3.60aa5ceb.js +++ b/assets/js/bda39da3.da603a75.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6874],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),u=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==u&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",b.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},1931:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>u,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const u={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-4.3/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-4.3/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/4.3/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/query-plan.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"version-4.3/docs",previous:{title:"Connecting security to your framework",permalink:"/docs/4.3/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/4.3/prefetch-method"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6874],{19365:(e,t,n)=>{n.d(t,{A:()=>o});var a=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>q});var a=n(58168),r=n(96540),l=n(20053),o=n(23104),u=n(56347),s=n(57485),i=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function h(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!h({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,i]=m({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=s??p;return h({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&u(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!h({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),i(e),f(e)}),[i,f,l]),tabValues:l}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:u,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=i[n].value;a!==u&&(p(t),s(a))},h=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},t)},i.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>c.push(e),onKeyDown:h,onClick:d},o,{className:(0,l.A)("tabs__item",b.tabItem,o?.className,{"tabs__item--active":u===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",b.tabList)},r.createElement(g,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function q(e){const t=(0,y.A)();return r.createElement(w,(0,a.A)({key:String(t)},e))}},1931:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>u,metadata:()=>i,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),o=n(19365);const u={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},s=void 0,i={unversionedId:"query-plan",id:"version-4.3/query-plan",title:"Query plan",description:"The problem",source:"@site/versioned_docs/version-4.3/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/4.3/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/query-plan.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"version-4.3/docs",previous:{title:"Connecting security to your framework",permalink:"/docs/4.3/implementing-security"},next:{title:"Prefetching records",permalink:"/docs/4.3/prefetch-method"}},c={},p=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],d={toc:p},h="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(h,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")))),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/bdb33130.227c6eff.js b/assets/js/bdb33130.8f685e3c.js similarity index 95% rename from assets/js/bdb33130.227c6eff.js rename to assets/js/bdb33130.8f685e3c.js index 437ad0b3ce..9c105d99bf 100644 --- a/assets/js/bdb33130.227c6eff.js +++ b/assets/js/bdb33130.8f685e3c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5049],{94273:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},l=void 0,s={unversionedId:"inheritance-interfaces",id:"inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/docs/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/next/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/inheritance-interfaces.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"docs",previous:{title:"Input types",permalink:"/docs/next/input-types"},next:{title:"Error handling",permalink:"/docs/next/error-handling"}},p={},c=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],o={toc:c},g="wrapper";function d(e){let{components:n,...t}=e;return(0,i.yg)(g,(0,a.A)({},o,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,i.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute on a PHP interface."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,i.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,i.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will translate in GraphQL schema as:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,i.yg)("p",null,"Please note that you do not need to put the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute again in the implementing class."),(0,i.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,i.yg)("p",null,"You don't have to explicitly put a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute on the class implementing the interface (though this\nis usually a good idea)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")),(0,i.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,i.yg)("p",null,"In the example above, because the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute, GraphQLite will\ncreate a ",(0,i.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5049],{94273:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>l,default:()=>m,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var a=t(58168),i=(t(96540),t(15680));t(67443);const r={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},l=void 0,s={unversionedId:"inheritance-interfaces",id:"inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/docs/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/next/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/inheritance-interfaces.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"docs",previous:{title:"Input types",permalink:"/docs/next/input-types"},next:{title:"Error handling",permalink:"/docs/next/error-handling"}},p={},c=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],o={toc:c},g="wrapper";function m(e){let{components:n,...t}=e;return(0,i.yg)(g,(0,a.A)({},o,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,i.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,i.yg)("p",null,"Let's say you have two classes, ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n")),(0,i.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n")),(0,i.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,i.yg)("p",null,"Written in ",(0,i.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,i.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,i.yg)("p",null,"The GraphQL ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,i.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,i.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,i.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,i.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute on a PHP interface."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,i.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,i.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("p",null,"This will translate in GraphQL schema as:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,i.yg)("p",null,"Please note that you do not need to put the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute again in the implementing class."),(0,i.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,i.yg)("p",null,"You don't have to explicitly put a ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute on the class implementing the interface (though this\nis usually a good idea)."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")),(0,i.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,i.yg)("p",null,"In the example above, because the ",(0,i.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Type]")," attribute, GraphQLite will\ncreate a ",(0,i.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/be1f0304.ec3e82e5.js b/assets/js/be1f0304.ded0473a.js similarity index 99% rename from assets/js/be1f0304.ec3e82e5.js rename to assets/js/be1f0304.ded0473a.js index 903646b499..311c71bbd5 100644 --- a/assets/js/be1f0304.ec3e82e5.js +++ b/assets/js/be1f0304.ded0473a.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6266],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},424:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},s=void 0,u={unversionedId:"extend-type",id:"version-5.0/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-5.0/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/5.0/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/extend-type.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"version-5.0/docs",previous:{title:"Autowiring services",permalink:"/docs/5.0/autowiring"},next:{title:"External type declaration",permalink:"/docs/5.0/external-type-declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6266],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},424:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},s=void 0,u={unversionedId:"extend-type",id:"version-5.0/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-5.0/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/5.0/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/extend-type.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"version-5.0/docs",previous:{title:"Autowiring services",permalink:"/docs/5.0/autowiring"},next:{title:"External type declaration",permalink:"/docs/5.0/external-type-declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/beccb025.2c15d7a4.js b/assets/js/beccb025.af0246bf.js similarity index 92% rename from assets/js/beccb025.2c15d7a4.js rename to assets/js/beccb025.af0246bf.js index f5bb761944..67ce914410 100644 --- a/assets/js/beccb025.2c15d7a4.js +++ b/assets/js/beccb025.af0246bf.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[247],{97386:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var n=a(58168),i=(a(96540),a(15680));a(67443);const o={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},r=void 0,s={unversionedId:"pagination",id:"version-4.0/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-4.0/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/4.0/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/pagination.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},sidebar:"version-4.0/docs",previous:{title:"File uploads",permalink:"/docs/4.0/file-uploads"},next:{title:"Custom types",permalink:"/docs/4.0/custom-types"}},l={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],u={toc:p},g="wrapper";function d(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,i.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,i.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,i.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,i.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, the GraphQLite Laravel bundle ",(0,i.yg)("a",{href:"laravel-package-advanced"},"comes with its own pagination system"),"."),(0,i.yg)("h2",{id:"installation"},"Installation"),(0,i.yg)("p",null,"You will need to install the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,i.yg)("h2",{id:"usage"},"Usage"),(0,i.yg)("p",null,"In your query, simply return a class that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")),(0,i.yg)("p",null,"Notice that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,i.yg)("li",{parentName:"ul"},"you MUST add a ",(0,i.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,i.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,i.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,i.yg)("p",null,'The "count" field returns the ',(0,i.yg)("strong",{parentName:"p"},"total count")," of items."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[247],{97386:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>p});var n=a(58168),i=(a(96540),a(15680));a(67443);const r={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},o=void 0,s={unversionedId:"pagination",id:"version-4.0/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-4.0/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/4.0/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/pagination.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination",original_id:"pagination"},sidebar:"version-4.0/docs",previous:{title:"File uploads",permalink:"/docs/4.0/file-uploads"},next:{title:"Custom types",permalink:"/docs/4.0/custom-types"}},l={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],u={toc:p},g="wrapper";function d(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,i.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,i.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,i.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,i.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, the GraphQLite Laravel bundle ",(0,i.yg)("a",{href:"laravel-package-advanced"},"comes with its own pagination system"),"."),(0,i.yg)("h2",{id:"installation"},"Installation"),(0,i.yg)("p",null,"You will need to install the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,i.yg)("h2",{id:"usage"},"Usage"),(0,i.yg)("p",null,"In your query, simply return a class that implements ",(0,i.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")),(0,i.yg)("p",null,"Notice that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,i.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,i.yg)("li",{parentName:"ul"},"you MUST add a ",(0,i.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,i.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,i.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,i.yg)("p",null,'The "count" field returns the ',(0,i.yg)("strong",{parentName:"p"},"total count")," of items."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/bf2a5963.e7d07140.js b/assets/js/bf2a5963.c913f417.js similarity index 98% rename from assets/js/bf2a5963.e7d07140.js rename to assets/js/bf2a5963.c913f417.js index 0ad747939e..abdcfa22da 100644 --- a/assets/js/bf2a5963.e7d07140.js +++ b/assets/js/bf2a5963.c913f417.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8919],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),s=a(56347),i=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function g(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,s.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,s]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,u]=m({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=i??p;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&s(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==s&&(p(t),i(n))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":s===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,f.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},45839:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>s,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const s={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},i=void 0,u={unversionedId:"pagination",id:"version-4.3/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-4.3/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/4.3/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/pagination.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},sidebar:"version-4.3/docs",previous:{title:"File uploads",permalink:"/docs/4.3/file-uploads"},next:{title:"Custom types",permalink:"/docs/4.3/custom-types"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(g,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,r.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,r.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,r.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,r.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"You will need to install the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"In your query, simply return a class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,r.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,r.yg)("p",null,'The "count" field returns the ',(0,r.yg)("strong",{parentName:"p"},"total count")," of items."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8919],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),s=a(56347),i=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function g(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,s.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,s]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,u]=m({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=i??p;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&s(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),h(e)}),[u,h,l]),tabValues:l}}var f=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:s,selectValue:i,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==s&&(p(t),i(n))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":s===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,f.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},45839:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>i,default:()=>m,frontMatter:()=>s,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const s={id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},i=void 0,u={unversionedId:"pagination",id:"version-4.3/pagination",title:"Paginating large result sets",description:"It is quite common to have to paginate over large result sets.",source:"@site/versioned_docs/version-4.3/pagination.mdx",sourceDirName:".",slug:"/pagination",permalink:"/docs/4.3/pagination",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/pagination.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"pagination",title:"Paginating large result sets",sidebar_label:"Pagination"},sidebar:"version-4.3/docs",previous:{title:"File uploads",permalink:"/docs/4.3/file-uploads"},next:{title:"Custom types",permalink:"/docs/4.3/custom-types"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(g,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"It is quite common to have to paginate over large result sets."),(0,r.yg)("p",null,"GraphQLite offers a simple way to do that using ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas"),"."),(0,r.yg)("p",null,"Porpaginas is a set of PHP interfaces that can be implemented by result iterators. It comes with a native support for\nPHP arrays, Doctrine and ",(0,r.yg)("a",{parentName:"p",href:"https://thecodingmachine.github.io/tdbm/doc/limit_offset_resultset.html"},"TDBM"),"."),(0,r.yg)("div",{class:"alert alert--warning"},"If you are a Laravel user, Eloquent does not come with a Porpaginas iterator. However, ",(0,r.yg)("a",{href:"laravel-package-advanced"},"the GraphQLite Laravel bundle comes with its own pagination system"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"You will need to install the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/beberlei/porpaginas"},"Porpaginas")," library to benefit from this feature."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"$ composer require beberlei/porpaginas\n")),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"In your query, simply return a class that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"Porpaginas\\Result"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Porpaginas\\Result\n {\n // Some code that returns a list of products\n\n // If you are using Doctrine, something like:\n return new Porpaginas\\Doctrine\\ORM\\ORMQueryResult($doctrineQuery);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")," or a class implementing ",(0,r.yg)("inlineCode",{parentName:"li"},"Porpaginas\\Result")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can paginate directly from your GraphQL query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"products {\n items(limit: 10, offset: 20) {\n id\n name\n }\n count\n}\n")),(0,r.yg)("p",null,'Results are wrapped into an item field. You can use the "limit" and "offset" parameters to apply pagination automatically.'),(0,r.yg)("p",null,'The "count" field returns the ',(0,r.yg)("strong",{parentName:"p"},"total count")," of items."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c007fb39.ded066fe.js b/assets/js/c007fb39.f54c7d5f.js similarity index 99% rename from assets/js/c007fb39.ded066fe.js rename to assets/js/c007fb39.f54c7d5f.js index f333e16f3a..1fde984374 100644 --- a/assets/js/c007fb39.ded066fe.js +++ b/assets/js/c007fb39.f54c7d5f.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[521],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},69904:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-6.0/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-6.0/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/6.0/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/error-handling.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/6.0/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/6.0/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/6.0/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/6.0/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[521],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var r=t(96540),a=t(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.A)(o.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>w});var r=t(58168),a=t(96540),o=t(20053),i=t(23104),l=t(56347),s=t(57485),u=t(31682),c=t(89466);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:r,default:a}}=e;return{value:n,label:t,attributes:r,default:a}}))}function h(e){const{values:n,children:t}=e;return(0,a.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(o),(0,a.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,o=h(e),[i,l]=(0,a.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=t.find((e=>e.default))??t[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,u]=g({queryString:t,groupId:r}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,c.Dv)(t);return[r,(0,a.useCallback)((e=>{t&&o.set(e)}),[t,o])]}({groupId:r}),m=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{m&&l(m)}),[m]);return{selectedValue:i,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),h=e=>{const n=e.currentTarget,t=c.indexOf(n),r=u[t].value;r!==l&&(p(n),s(r))},d=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return a.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:h},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),t??n)})))}function x(e){let{lazy:n,children:t,selectedValue:r}=e;const o=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,a.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return a.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},a.createElement(b,(0,r.A)({},e,n)),a.createElement(x,(0,r.A)({},e,n)))}function w(e){const n=(0,m.A)();return a.createElement(v,(0,r.A)({key:String(n)},e))}},69904:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var r=t(58168),a=(t(96540),t(15680)),o=(t(67443),t(11470)),i=t(19365);const l={id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},s=void 0,u={unversionedId:"error-handling",id:"version-6.0/error-handling",title:"Error handling",description:'In GraphQL, when an error occurs, the server must add an "error" entry in the response.',source:"@site/versioned_docs/version-6.0/error-handling.mdx",sourceDirName:".",slug:"/error-handling",permalink:"/docs/6.0/error-handling",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/error-handling.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"error-handling",title:"Error handling",sidebar_label:"Error handling"},sidebar:"docs",previous:{title:"Inheritance and interfaces",permalink:"/docs/6.0/inheritance-interfaces"},next:{title:"User input validation",permalink:"/docs/6.0/validation"}},c={},p=[{value:"HTTP response code",id:"http-response-code",level:2},{value:"Customizing the category",id:"customizing-the-category",level:2},{value:"Customizing the extensions section",id:"customizing-the-extensions-section",level:2},{value:"Writing your own exceptions",id:"writing-your-own-exceptions",level:2},{value:"Many errors for one exception",id:"many-errors-for-one-exception",level:2},{value:"Webonyx exceptions",id:"webonyx-exceptions",level:2},{value:"Behaviour of exceptions that do not implement ClientAware",id:"behaviour-of-exceptions-that-do-not-implement-clientaware",level:2}],h={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,a.yg)(d,(0,r.A)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,a.yg)("p",null,'In GraphQL, when an error occurs, the server must add an "error" entry in the response.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Name for character with ID 1002 could not be fetched.",\n "locations": [ { "line": 6, "column": 7 } ],\n "path": [ "hero", "heroFriends", 1, "name" ],\n "extensions": {\n "category": "Exception"\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"You can generate such errors with GraphQLite by throwing a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),"."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException;\n\nthrow new GraphQLException("Exception message");\n')),(0,a.yg)("h2",{id:"http-response-code"},"HTTP response code"),(0,a.yg)("p",null,"By default, when you throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", the HTTP status code will be 500."),(0,a.yg)("p",null,"If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'// This exception will generate a HTTP 404 status code\nthrow new GraphQLException("Not found", 404);\n')),(0,a.yg)("div",{class:"alert alert--info"},"GraphQL allows to have several errors for one request. If you have several",(0,a.yg)("code",null,"GraphQLException")," thrown for the same request, the HTTP status code used will be the highest one."),(0,a.yg)("h2",{id:"customizing-the-category"},"Customizing the category"),(0,a.yg)("p",null,'By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the\n4th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'throw new GraphQLException("Not found", 404, null, "NOT_FOUND");\n')),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Not found",\n "extensions": {\n "category": "NOT_FOUND"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"customizing-the-extensions-section"},"Customizing the extensions section"),(0,a.yg)("p",null,'You can customize the whole "extensions" section with the 5th parameter of the constructor:'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"throw new GraphQLException(\"Field required\", 400, null, \"VALIDATION\", ['field' => 'name']);\n")),(0,a.yg)("p",null,"will generate:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "Field required",\n "extensions": {\n "category": "VALIDATION",\n "field": "name"\n }\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"writing-your-own-exceptions"},"Writing your own exceptions"),(0,a.yg)("p",null,"Rather that throwing the base ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLException"),", you should consider writing your own exception."),(0,a.yg)("p",null,"Any exception that implements interface ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface"),' will be displayed\nin the GraphQL "errors" section.'),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'class ValidationException extends Exception implements GraphQLExceptionInterface\n{\n /**\n * Returns true when exception message is safe to be displayed to a client.\n */\n public function isClientSafe(): bool\n {\n return true;\n }\n\n /**\n * Returns string describing a category of the error.\n *\n * Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.\n */\n public function getCategory(): string\n {\n return \'VALIDATION\';\n }\n\n /**\n * Returns the "extensions" object attached to the GraphQL error.\n *\n * @return array\n */\n public function getExtensions(): array\n {\n return [];\n }\n}\n')),(0,a.yg)("h2",{id:"many-errors-for-one-exception"},"Many errors for one exception"),(0,a.yg)("p",null,"Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can\nthrow only one exception."),(0,a.yg)("p",null,"If you want to display several exceptions, you can bundle these exceptions in a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQLAggregateException")," that you can\nthrow."),(0,a.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,a.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n#[Query]\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n"))),(0,a.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException;\n\n/**\n * @Query\n */\npublic function createProduct(string $name, float $price): Product\n{\n $exceptions = new GraphQLAggregateException();\n\n if ($name === '') {\n $exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));\n }\n if ($price <= 0) {\n $exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));\n }\n\n if ($exceptions->hasExceptions()) {\n throw $exceptions;\n }\n}\n")))),(0,a.yg)("h2",{id:"webonyx-exceptions"},"Webonyx exceptions"),(0,a.yg)("p",null,"GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can\nalso be used in GraphQLite. This means you can throw a ",(0,a.yg)("inlineCode",{parentName:"p"},"GraphQL\\Error\\Error")," exception or any exception implementing\n",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql"},(0,a.yg)("inlineCode",{parentName:"a"},"GraphQL\\Error\\ClientAware")," interface")),(0,a.yg)("p",null,"Actually, the ",(0,a.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLExceptionInterface")," extends Webonyx's ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," interface."),(0,a.yg)("h2",{id:"behaviour-of-exceptions-that-do-not-implement-clientaware"},"Behaviour of exceptions that do not implement ClientAware"),(0,a.yg)("p",null,"If an exception that does not implement ",(0,a.yg)("inlineCode",{parentName:"p"},"ClientAware")," is thrown, by default, GraphQLite will not catch it."),(0,a.yg)("p",null,"The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page."),(0,a.yg)("p",null,"You can ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/error-handling/#debugging-tools"},"change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors"),".\nThe way you adjust the error settings depends on the framework you are using (",(0,a.yg)("a",{parentName:"p",href:"/docs/6.0/symfony-bundle"},"Symfony"),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/6.0/laravel-package"},"Laravel"),")."),(0,a.yg)("div",{class:"alert alert--info"},'To be clear: we strongly discourage changing this setting. We strongly believe that the default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions designed for GraphQL).'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c0fa6485.d007f2cc.js b/assets/js/c0fa6485.2a80d401.js similarity index 97% rename from assets/js/c0fa6485.d007f2cc.js rename to assets/js/c0fa6485.2a80d401.js index bf971ab288..6cbae5a073 100644 --- a/assets/js/c0fa6485.d007f2cc.js +++ b/assets/js/c0fa6485.2a80d401.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8414],{76890:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes",original_id:"doctrine-annotations-attributes"},r=void 0,s={unversionedId:"doctrine-annotations-attributes",id:"version-4.1/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-4.1/doctrine_annotations_attributes.md",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/4.1/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/doctrine_annotations_attributes.md",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes",original_id:"doctrine-annotations-attributes"},sidebar:"version-4.1/docs",previous:{title:"Migrating",permalink:"/docs/4.1/migrating"},next:{title:"Annotations reference",permalink:"/docs/4.1/annotations_reference"}},l={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],u={toc:p},d="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(d,(0,a.A)({},u,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,i.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in GraphQLite 5.0"),(0,i.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support.\nThis was the purpose of the ',(0,i.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,i.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"Please note that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,i.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,i.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,i.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,i.yg)("div",{class:"alert alert--info"},(0,i.yg)("strong",null,"Heads up!"),"Some IDEs provide support for Doctrine annotations:",(0,i.yg)("ul",null,(0,i.yg)("li",null,"PhpStorm via the ",(0,i.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,i.yg)("li",null,"Eclipse via the ",(0,i.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,i.yg)("li",null,"Netbeans has native support")),(0,i.yg)("p",null,"We strongly recommend using an IDE that has Doctrine annotations support.")),(0,i.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,i.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,i.yg)("p",null,"The same code can be written this way:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,i.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,i.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,i.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,i.yg)("p",null,"They support the same attributes too."),(0,i.yg)("p",null,"A few notable differences:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,i.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,i.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,i.yg)("p",null,"Let's take an example with the ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.1/autowiring"},(0,i.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 7+")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 8")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8414],{76890:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));n(67443);const o={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes",original_id:"doctrine-annotations-attributes"},r=void 0,s={unversionedId:"doctrine-annotations-attributes",id:"version-4.1/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-4.1/doctrine_annotations_attributes.md",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/4.1/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/doctrine_annotations_attributes.md",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes",original_id:"doctrine-annotations-attributes"},sidebar:"version-4.1/docs",previous:{title:"Migrating",permalink:"/docs/4.1/migrating"},next:{title:"Annotations reference",permalink:"/docs/4.1/annotations_reference"}},l={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],u={toc:p},d="wrapper";function c(t){let{components:e,...n}=t;return(0,i.yg)(d,(0,a.A)({},u,n,{components:e,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,i.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,i.yg)("div",{class:"alert alert--warning"},(0,i.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in GraphQLite 5.0"),(0,i.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support.\nThis was the purpose of the ',(0,i.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,i.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"Please note that:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,i.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,i.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,i.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,i.yg)("div",{class:"alert alert--info"},(0,i.yg)("strong",null,"Heads up!"),"Some IDEs provide support for Doctrine annotations:",(0,i.yg)("ul",null,(0,i.yg)("li",null,"PhpStorm via the ",(0,i.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,i.yg)("li",null,"Eclipse via the ",(0,i.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,i.yg)("li",null,"Netbeans has native support")),(0,i.yg)("p",null,"We strongly recommend using an IDE that has Doctrine annotations support.")),(0,i.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,i.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,i.yg)("p",null,"The same code can be written this way:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,i.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,i.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,i.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,i.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,i.yg)("p",null,"They support the same attributes too."),(0,i.yg)("p",null,"A few notable differences:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,i.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,i.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,i.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,i.yg)("p",null,"Let's take an example with the ",(0,i.yg)("a",{parentName:"p",href:"/docs/4.1/autowiring"},(0,i.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 7+")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"PHP 8")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n")))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c10d4a63.bd5619e8.js b/assets/js/c10d4a63.a576095a.js similarity index 99% rename from assets/js/c10d4a63.bd5619e8.js rename to assets/js/c10d4a63.a576095a.js index ecf1b7f89c..6fb5be93ea 100644 --- a/assets/js/c10d4a63.bd5619e8.js +++ b/assets/js/c10d4a63.a576095a.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1235],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),p=a(57485),o=a(31682),s=a(89466);function d(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function c(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,o.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,p.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=c(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[p,o]=m({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=p??d;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),o(e),h(e)}),[o,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:p,tabValues:o}=e;const s=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=s.indexOf(t),n=o[a].value;n!==i&&(d(t),p(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},o.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:c},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},71592:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>p,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>d});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},p=void 0,o={unversionedId:"multiple-output-types",id:"version-6.0/multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.0/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/6.0/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/multiple-output-types.mdx",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"docs",previous:{title:"Extending an input type",permalink:"/docs/6.0/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/6.0/symfony-bundle-advanced"}},s={},d=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],c={toc:d},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/external-type-declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1235],{19365:(e,t,a)=>{a.d(t,{A:()=>r});var n=a(96540),l=a(20053);const u={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:a,className:r}=e;return n.createElement("div",{role:"tabpanel",className:(0,l.A)(u.tabItem,r),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>P});var n=a(58168),l=a(96540),u=a(20053),r=a(23104),i=a(56347),p=a(57485),o=a(31682),s=a(89466);function d(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:l}}=e;return{value:t,label:a,attributes:n,default:l}}))}function c(e){const{values:t,children:a}=e;return(0,l.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,o.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function y(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,i.W6)(),u=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,p.aZ)(u),(0,l.useCallback)((e=>{if(!u)return;const t=new URLSearchParams(n.location.search);t.set(u,e),n.replace({...n.location,search:t.toString()})}),[u,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,u=c(e),[r,i]=(0,l.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:u}))),[p,o]=m({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,u]=(0,s.Dv)(a);return[n,(0,l.useCallback)((e=>{a&&u.set(e)}),[a,u])]}({groupId:n}),g=(()=>{const e=p??d;return y({value:e,tabValues:u})?e:null})();(0,l.useLayoutEffect)((()=>{g&&i(g)}),[g]);return{selectedValue:r,selectValue:(0,l.useCallback)((e=>{if(!y({value:e,tabValues:u}))throw new Error(`Can't select invalid tab value=${e}`);i(e),o(e),h(e)}),[o,h,u]),tabValues:u}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:p,tabValues:o}=e;const s=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),c=e=>{const t=e.currentTarget,a=s.indexOf(t),n=o[a].value;n!==i&&(d(t),p(n))},y=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=s.indexOf(e.currentTarget)+1;t=s[a]??s[0];break}case"ArrowLeft":{const a=s.indexOf(e.currentTarget)-1;t=s[a]??s[s.length-1];break}}t?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,u.A)("tabs",{"tabs--block":a},t)},o.map((e=>{let{value:t,label:a,attributes:r}=e;return l.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>s.push(e),onKeyDown:y,onClick:c},r,{className:(0,u.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const u=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=u.find((e=>e.props.value===n));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},u.map(((e,t)=>(0,l.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function T(e){const t=h(e);return l.createElement("div",{className:(0,u.A)("tabs-container",f.tabList)},l.createElement(b,(0,n.A)({},e,t)),l.createElement(v,(0,n.A)({},e,t)))}function P(e){const t=(0,g.A)();return l.createElement(T,(0,n.A)({key:String(t)},e))}},71592:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>p,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>d});var n=a(58168),l=(a(96540),a(15680)),u=(a(67443),a(11470)),r=a(19365);const i={id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},p=void 0,o={unversionedId:"multiple-output-types",id:"version-6.0/multiple-output-types",title:"Mapping multiple output types for the same class",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.0/multiple-output-types.mdx",sourceDirName:".",slug:"/multiple-output-types",permalink:"/docs/6.0/multiple-output-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/multiple-output-types.mdx",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"multiple-output-types",title:"Mapping multiple output types for the same class",sidebar_label:"Class with multiple output types"},sidebar:"docs",previous:{title:"Extending an input type",permalink:"/docs/6.0/extend-input-type"},next:{title:"Symfony specific features",permalink:"/docs/6.0/symfony-bundle-advanced"}},s={},d=[{value:"Example",id:"example",level:2},{value:"Extending a non-default type",id:"extending-a-non-default-type",level:2}],c={toc:d},y="wrapper";function m(e){let{components:t,...a}=e;return(0,l.yg)(y,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,l.yg)("small",null,"Available in GraphQLite 4.0+"),(0,l.yg)("p",null,"In most cases, you have one PHP class and you want to map it to one GraphQL output type."),(0,l.yg)("p",null,"But in very specific cases, you may want to use different GraphQL output type for the same class.\nFor instance, depending on the context, you might want to prevent the user from accessing some fields of your object."),(0,l.yg)("p",null,'To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("h2",{id:"example"},"Example"),(0,l.yg)("p",null,"Here is an example. Say we are manipulating products. When I query a ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," details, I want to have access to all fields.\nBut for some reason, I don't want to expose the price field of a product if I query the list of all products."),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"Product"),' class is declaring a classic GraphQL output type named "Product".'),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[Type(class: Product::class, name: "LimitedProduct", default: false)]\n#[SourceField(name: "name")]\nclass LimitedProductType\n{\n // ...\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type(class=Product::class, name="LimitedProduct", default=false)\n * @SourceField(name="name")\n */\nclass LimitedProductType\n{\n // ...\n}\n')))),(0,l.yg)("p",null,"The ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType")," also declares an ",(0,l.yg)("a",{parentName:"p",href:"/docs/6.0/external-type-declaration"},'"external" type')," mapping the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class.\nBut pay special attention to the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation."),(0,l.yg)("p",null,"First of all, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},'name="LimitedProduct"'),'. This is useful to avoid having colliding names with the "Product" GraphQL output type\nthat is already declared.'),(0,l.yg)("p",null,"Then, we specify ",(0,l.yg)("inlineCode",{parentName:"p"},"default=false"),". This means that by default, the ",(0,l.yg)("inlineCode",{parentName:"p"},"Product")," class should not be mapped to the ",(0,l.yg)("inlineCode",{parentName:"p"},"LimitedProductType"),".\nThis type will only be used when we explicitly request it."),(0,l.yg)("p",null,"Finally, we can write our requests:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n */\n #[Field]\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @return Product[]\n */\n #[Field(outputType: "[LimitedProduct!]!")]\n public function getProducts(): array { /* ... */ }\n}\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'class ProductController\n{\n /**\n * This field will use the default type.\n *\n * @Field\n */\n public function getProduct(int $id): Product { /* ... */ }\n\n /**\n * Because we use the "outputType" attribute, this field will use the other type.\n *\n * @Field(outputType="[LimitedProduct!]!")\n * @return Product[]\n */\n public function getProducts(): array { /* ... */ }\n}\n')))),(0,l.yg)("p",null,'Notice how the "outputType" attribute is used in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@Field")," annotation to force the output type."),(0,l.yg)("p",null,"Is a result, when the end user calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"product")," query, we will have the possibility to fetch the ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," and ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," fields,\nbut if he calls the ",(0,l.yg)("inlineCode",{parentName:"p"},"products")," query, each product in the list will have a ",(0,l.yg)("inlineCode",{parentName:"p"},"name")," field but no ",(0,l.yg)("inlineCode",{parentName:"p"},"price")," field. We managed\nto successfully expose a different set of fields based on the query context."),(0,l.yg)("h2",{id:"extending-a-non-default-type"},"Extending a non-default type"),(0,l.yg)("p",null,"If you want to extend a type using the ",(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation and if this type is declared as non-default,\nyou need to target the type by name instead of by class."),(0,l.yg)("p",null,"So instead of writing:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,l.yg)("p",null,"you will write:"),(0,l.yg)(u.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(r.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'#[ExtendType(name: "LimitedProduct")]\n'))),(0,l.yg)(r.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @ExtendType(name="LimitedProduct")\n */\n')))),(0,l.yg)("p",null,'Notice how we use the "name" attribute instead of the "class" attribute in the ',(0,l.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c1fe0282.bb7327d3.js b/assets/js/c1fe0282.1aa8f919.js similarity index 99% rename from assets/js/c1fe0282.bb7327d3.js rename to assets/js/c1fe0282.1aa8f919.js index 0aa24fec92..b767452891 100644 --- a/assets/js/c1fe0282.bb7327d3.js +++ b/assets/js/c1fe0282.1aa8f919.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6912],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var r=a(96540),t=a(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>$});var r=a(58168),t=a(96540),o=a(20053),i=a(23104),l=a(56347),s=a(57485),c=a(31682),p=a(89466);function u(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:r,default:t}}=e;return{value:n,label:a,attributes:r,default:t}}))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function h(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:a}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,c]=m({queryString:a,groupId:r}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,p.Dv)(a);return[r,(0,t.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:r}),g=(()=>{const e=s??u;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),c(e),y(e)}),[c,y,o]),tabValues:o}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:s,tabValues:c}=e;const p=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),d=e=>{const n=e.currentTarget,a=p.indexOf(n),r=c[a].value;r!==l&&(u(n),s(r))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;n=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;n=p[a]??p[p.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return t.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>p.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),a??n)})))}function w(e){let{lazy:n,children:a,selectedValue:r}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,r.A)({},e,n)),t.createElement(w,(0,r.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,r.A)({key:String(n)},e))}},70157:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>c,toc:()=>u});var r=a(58168),t=(a(96540),a(15680)),o=(a(67443),a(11470)),i=a(19365);const l={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},s=void 0,c={unversionedId:"other-frameworks",id:"version-5.0/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-5.0/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/5.0/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/other-frameworks.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"version-5.0/docs",previous:{title:"Universal service providers",permalink:"/docs/5.0/universal-service-providers"},next:{title:"Queries",permalink:"/docs/5.0/queries"}},p={},u=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],d={toc:u},h="wrapper";function m(e){let{components:n,...a}=e;return(0,t.yg)(h,(0,r.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'$builder->setUrl("/graphql"); // Modify the URL endpoint (defaults to /graphql)\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object. Use this object to configure Webonyx in details.\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n')),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,t.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n"))),(0,t.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")))),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6912],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var r=a(96540),t=a(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return r.createElement("div",{role:"tabpanel",className:(0,t.A)(o.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>$});var r=a(58168),t=a(96540),o=a(20053),i=a(23104),l=a(56347),s=a(57485),c=a(31682),p=a(89466);function u(e){return function(e){return t.Children.map(e,(e=>{if(!e||(0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:r,default:t}}=e;return{value:n,label:a,attributes:r,default:t}}))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function h(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function m(e){let{queryString:n=!1,groupId:a}=e;const r=(0,l.W6)(),o=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,s.aZ)(o),(0,t.useCallback)((e=>{if(!o)return;const n=new URLSearchParams(r.location.search);n.set(o,e),r.replace({...r.location,search:n.toString()})}),[o,r])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[i,l]=(0,t.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!h({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const r=a.find((e=>e.default))??a[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:n,tabValues:o}))),[s,c]=m({queryString:a,groupId:r}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[r,o]=(0,p.Dv)(a);return[r,(0,t.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:r}),g=(()=>{const e=s??u;return h({value:e,tabValues:o})?e:null})();(0,t.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:i,selectValue:(0,t.useCallback)((e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),c(e),y(e)}),[c,y,o]),tabValues:o}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:l,selectValue:s,tabValues:c}=e;const p=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),d=e=>{const n=e.currentTarget,a=p.indexOf(n),r=c[a].value;r!==l&&(u(n),s(r))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;n=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;n=p[a]??p[p.length-1];break}}n?.focus()};return t.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return t.createElement("li",(0,r.A)({role:"tab",tabIndex:l===n?0:-1,"aria-selected":l===n,key:n,ref:e=>p.push(e),onKeyDown:h,onClick:d},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":l===n})}),a??n)})))}function w(e){let{lazy:n,children:a,selectedValue:r}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=o.find((e=>e.props.value===r));return e?(0,t.cloneElement)(e,{className:"margin-top--md"}):null}return t.createElement("div",{className:"margin-top--md"},o.map(((e,n)=>(0,t.cloneElement)(e,{key:n,hidden:e.props.value!==r}))))}function v(e){const n=y(e);return t.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},t.createElement(b,(0,r.A)({},e,n)),t.createElement(w,(0,r.A)({},e,n)))}function $(e){const n=(0,g.A)();return t.createElement(v,(0,r.A)({key:String(n)},e))}},70157:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>c,toc:()=>u});var r=a(58168),t=(a(96540),a(15680)),o=(a(67443),a(11470)),i=a(19365);const l={id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},s=void 0,c={unversionedId:"other-frameworks",id:"version-5.0/other-frameworks",title:"Getting started with any framework",description:"Installation",source:"@site/versioned_docs/version-5.0/other-frameworks.mdx",sourceDirName:".",slug:"/other-frameworks",permalink:"/docs/5.0/other-frameworks",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/other-frameworks.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"other-frameworks",title:"Getting started with any framework",sidebar_label:"Other frameworks / No framework"},sidebar:"version-5.0/docs",previous:{title:"Universal service providers",permalink:"/docs/5.0/universal-service-providers"},next:{title:"Queries",permalink:"/docs/5.0/queries"}},p={},u=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"GraphQLite context",id:"graphqlite-context",level:3},{value:"Minimal example",id:"minimal-example",level:2},{value:"PSR-15 Middleware",id:"psr-15-middleware",level:2},{value:"Example",id:"example",level:3}],d={toc:u},h="wrapper";function m(e){let{components:n,...a}=e;return(0,t.yg)(h,(0,r.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,t.yg)("h2",{id:"installation"},"Installation"),(0,t.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite\n")),(0,t.yg)("h2",{id:"requirements"},"Requirements"),(0,t.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},"A PSR-11 compatible container"),(0,t.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,t.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,t.yg)("p",null,"GraphQLite relies on the ",(0,t.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and we also provide a ",(0,t.yg)("a",{parentName:"p",href:"#psr-15-middleware"},"PSR-15 middleware"),"."),(0,t.yg)("h2",{id:"integration"},"Integration"),(0,t.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. We provide a ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class to create such a schema:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\SchemaFactory;\n\n// $cache is a PSR-16 compatible cache\n// $container is a PSR-11 compatible container\n$factory = new SchemaFactory($cache, $container);\n$factory->addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n")),(0,t.yg)("p",null,"You can now use this schema with ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/getting-started/#hello-world"},"Webonyx GraphQL facade"),"\nor the ",(0,t.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/executing-queries/#using-server"},"StandardServer class"),"."),(0,t.yg)("p",null,"The ",(0,t.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," class also comes with a number of methods that you can use to customize your GraphQLite settings."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'// Configure an authentication service (to resolve the @Logged annotations).\n$factory->setAuthenticationService(new VoidAuthenticationService());\n// Configure an authorization service (to resolve the @Right annotations).\n$factory->setAuthorizationService(new VoidAuthorizationService());\n// Change the naming convention of GraphQL types globally.\n$factory->setNamingStrategy(new NamingStrategy());\n// Add a custom type mapper.\n$factory->addTypeMapper($typeMapper);\n// Add a custom type mapper using a factory to create it.\n// Type mapper factories are useful if you need to inject the "recursive type mapper" into your type mapper constructor.\n$factory->addTypeMapperFactory($typeMapperFactory);\n// Add a root type mapper.\n$factory->addRootTypeMapper($rootTypeMapper);\n// Add a parameter mapper.\n$factory->addParameterMapper($parameterMapper);\n// Add a query provider. These are used to find queries and mutations in the application.\n$factory->addQueryProvider($queryProvider);\n// Add a query provider using a factory to create it.\n// Query provider factories are useful if you need to inject the "fields builder" into your query provider constructor.\n$factory->addQueryProviderFactory($queryProviderFactory);\n// Add custom options to the Webonyx underlying Schema.\n$factory->setSchemaConfig($schemaConfig);\n// Configures the time-to-live for the GraphQLite cache. Defaults to 2 seconds in dev mode.\n$factory->setGlobTtl(2);\n// Enables prod-mode (cache settings optimized for best performance).\n// This is a shortcut for `$schemaFactory->setGlobTtl(null)`\n$factory->prodMode();\n// Enables dev-mode (this is the default mode: cache settings optimized for best developer experience).\n// This is a shortcut for `$schemaFactory->setGlobTtl(2)`\n$factory->devMode();\n')),(0,t.yg)("h3",{id:"graphqlite-context"},"GraphQLite context"),(0,t.yg)("p",null,'Webonyx allows you pass a "context" object when running a query.\nFor some GraphQLite features to work (namely: the prefetch feature), GraphQLite needs you to initialize the Webonyx context\nwith an instance of the ',(0,t.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Context\\Context")," class."),(0,t.yg)("p",null,"For instance:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Context\\Context;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n")),(0,t.yg)("h2",{id:"minimal-example"},"Minimal example"),(0,t.yg)("p",null,"The smallest working example using no framework is:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"addControllerNamespace('App\\\\Controllers\\\\')\n ->addTypeNamespace('App\\\\');\n\n$schema = $factory->createSchema();\n\n$rawInput = file_get_contents('php://input');\n$input = json_decode($rawInput, true);\n$query = $input['query'];\n$variableValues = isset($input['variables']) ? $input['variables'] : null;\n\n$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);\n$output = $result->toArray();\n\nheader('Content-Type: application/json');\necho json_encode($output);\n")),(0,t.yg)("h2",{id:"psr-15-middleware"},"PSR-15 Middleware"),(0,t.yg)("p",null,"When using a framework, you will need a way to route your HTTP requests to the ",(0,t.yg)("inlineCode",{parentName:"p"},"webonyx/graphql-php")," library."),(0,t.yg)("p",null,"If the framework you are using is compatible with PSR-15 (like Slim PHP or Zend-Expressive / Laminas), GraphQLite\ncomes with a PSR-15 middleware out of the box."),(0,t.yg)("p",null,"In order to get an instance of this middleware, you can use the ",(0,t.yg)("inlineCode",{parentName:"p"},"Psr15GraphQLMiddlewareBuilder")," builder class:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},"// $schema is an instance of the GraphQL schema returned by SchemaFactory::createSchema (see previous chapter)\n$builder = new Psr15GraphQLMiddlewareBuilder($schema);\n\n$middleware = $builder->createMiddleware();\n\n// You can now inject your middleware in your favorite PSR-15 compatible framework.\n// For instance:\n$zendMiddlewarePipe->pipe($middleware);\n")),(0,t.yg)("p",null,"The builder offers a number of setters to modify its behaviour:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php"},'$builder->setUrl("/graphql"); // Modify the URL endpoint (defaults to /graphql)\n$config = $builder->getConfig(); // Returns a Webonyx ServerConfig object. Use this object to configure Webonyx in details.\n$builder->setConfig($config);\n\n$builder->setResponseFactory(new ResponseFactory()); // Set a PSR-18 ResponseFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setStreamFactory(new StreamFactory()); // Set a PSR-18 StreamFactory (not needed if you are using zend-framework/zend-diactoros ^2\n$builder->setHttpCodeDecider(new HttpCodeDecider()); // Set a class in charge of deciding the HTTP status code based on the response.\n')),(0,t.yg)("h3",{id:"example"},"Example"),(0,t.yg)("p",null,"In this example, we will focus on getting a working version of GraphQLite using:"),(0,t.yg)("ul",null,(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("a",{parentName:"li",href:"https://docs.zendframework.com/zend-stratigility/"},"Zend Stratigility")," as a PSR-15 server"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"mouf/picotainer")," (a micro-container) for the PSR-11 container"),(0,t.yg)("li",{parentName:"ul"},(0,t.yg)("inlineCode",{parentName:"li"},"symfony/cache ")," for the PSR-16 cache")),(0,t.yg)("p",null,"The choice of the libraries is really up to you. You can adapt it based on your needs."),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-json",metastring:'title="composer.json"',title:'"composer.json"'},'{\n "autoload": {\n "psr-4": {\n "App\\\\": "src/"\n }\n },\n "require": {\n "thecodingmachine/graphqlite": "^4",\n "zendframework/zend-diactoros": "^2",\n "zendframework/zend-stratigility": "^3",\n "zendframework/zend-httphandlerrunner": "^1.0",\n "mouf/picotainer": "^1.1",\n "symfony/cache": "^4.2"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="index.php"',title:'"index.php"'},"get(MiddlewarePipe::class),\n new SapiStreamEmitter(),\n $serverRequestFactory,\n $errorResponseGenerator\n);\n$runner->run();\n")),(0,t.yg)("p",null,"Here we are initializing a Zend ",(0,t.yg)("inlineCode",{parentName:"p"},"RequestHandler")," (it receives requests) and we pass it to a Zend Stratigility ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe"),".\nThis ",(0,t.yg)("inlineCode",{parentName:"p"},"MiddlewarePipe")," comes from the container declared in the ",(0,t.yg)("inlineCode",{parentName:"p"},"config/container.php")," file:"),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'}," function(ContainerInterface $container) {\n $pipe = new MiddlewarePipe();\n $pipe->pipe($container->get(WebonyxGraphqlMiddleware::class));\n return $pipe;\n },\n // The WebonyxGraphqlMiddleware is a PSR-15 compatible\n // middleware that exposes Webonyx schemas.\n WebonyxGraphqlMiddleware::class => function(ContainerInterface $container) {\n $builder = new Psr15GraphQLMiddlewareBuilder($container->get(Schema::class));\n return $builder->createMiddleware();\n },\n CacheInterface::class => function() {\n return new ApcuCache();\n },\n Schema::class => function(ContainerInterface $container) {\n // The magic happens here. We create a schema using GraphQLite SchemaFactory.\n $factory = new SchemaFactory($container->get(CacheInterface::class), $container);\n $factory->addControllerNamespace('App\\\\Controllers\\\\');\n $factory->addTypeNamespace('App\\\\');\n return $factory->createSchema();\n }\n]);\n")),(0,t.yg)("p",null,"Now, we need to add a first query and therefore create a controller.\nThe application will look into the ",(0,t.yg)("inlineCode",{parentName:"p"},"App\\Controllers")," namespace for GraphQLite controllers."),(0,t.yg)("p",null,"It assumes that the container has an entry whose name is the controller's fully qualified class name."),(0,t.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,t.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n #[Query]\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n"))),(0,t.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="src/Controllers/MyController.php"',title:'"src/Controllers/MyController.php"'},"namespace App\\Controllers;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\n\nclass MyController\n{\n /**\n * @Query\n */\n public function hello(string $name): string\n {\n return 'Hello '.$name;\n }\n}\n")))),(0,t.yg)("pre",null,(0,t.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/container.php"',title:'"config/container.php"'},"use App\\Controllers\\MyController;\n\nreturn new Picotainer([\n // ...\n\n // We declare the controller in the container.\n MyController::class => function() {\n return new MyController();\n },\n]);\n")),(0,t.yg)("p",null,"And we are done! You can now test your query using your favorite GraphQL client."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c275698c.e2579a98.js b/assets/js/c275698c.48993737.js similarity index 99% rename from assets/js/c275698c.e2579a98.js rename to assets/js/c275698c.48993737.js index 22e5b93e6f..6a542774f1 100644 --- a/assets/js/c275698c.e2579a98.js +++ b/assets/js/c275698c.48993737.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2248],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},4743:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},s=void 0,u={unversionedId:"extend-type",id:"version-4.2/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-4.2/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/4.2/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/extend-type.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"version-4.2/docs",previous:{title:"Autowiring services",permalink:"/docs/4.2/autowiring"},next:{title:"External type declaration",permalink:"/docs/4.2/external-type-declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2248],{19365:(e,n,t)=>{t.d(n,{A:()=>i});var a=t(96540),r=t(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:t,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),r=t(96540),l=t(20053),i=t(23104),o=t(56347),s=t(57485),u=t(31682),p=t(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:r}}=e;return{value:n,label:t,attributes:a,default:r}}))}function d(e){const{values:n,children:t}=e;return(0,r.useMemo)((()=>{const e=n??c(t);return function(e){const n=(0,u.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function h(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),l=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(a.location.search);n.set(l,e),a.replace({...a.location,search:n.toString()})}),[l,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:l}))),[s,u]=h({queryString:t,groupId:a}),[c,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,l]=(0,p.Dv)(t);return[a,(0,r.useCallback)((e=>{t&&l.set(e)}),[t,l])]}({groupId:a}),m=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{m&&o(m)}),[m]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var m=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const n=e.currentTarget,t=p.indexOf(n),a=u[t].value;a!==o&&(c(n),s(a))},g=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=p.indexOf(e.currentTarget)+1;n=p[t]??p[0];break}case"ArrowLeft":{const t=p.indexOf(e.currentTarget)-1;n=p[t]??p[p.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":t},n)},u.map((e=>{let{value:n,label:t,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const l=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function T(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,a.A)({},e,n)),r.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,m.A)();return r.createElement(T,(0,a.A)({key:String(n)},e))}},4743:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var a=t(58168),r=(t(96540),t(15680)),l=(t(67443),t(11470)),i=t(19365);const o={id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},s=void 0,u={unversionedId:"extend-type",id:"version-4.2/extend-type",title:"Extending a type",description:"Fields exposed in a GraphQL type do not need to be all part of the same class.",source:"@site/versioned_docs/version-4.2/extend-type.mdx",sourceDirName:".",slug:"/extend-type",permalink:"/docs/4.2/extend-type",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/extend-type.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"extend-type",title:"Extending a type",sidebar_label:"Extending a type"},sidebar:"version-4.2/docs",previous:{title:"Autowiring services",permalink:"/docs/4.2/autowiring"},next:{title:"External type declaration",permalink:"/docs/4.2/external-type-declaration"}},p={},c=[],d={toc:c},g="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(g,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"Fields exposed in a GraphQL type do not need to be all part of the same class."),(0,r.yg)("p",null,"Use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation to add additional fields to a type that is already declared."),(0,r.yg)("div",{class:"alert alert--info"},"Extending a type has nothing to do with type inheritance. If you are looking for a way to expose a class and its children classes, have a look at the ",(0,r.yg)("a",{href:"inheritance-interfaces"},"Inheritance")," section"),(0,r.yg)("p",null,"Let's assume you have a ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class. In order to get the name of a product, there is no ",(0,r.yg)("inlineCode",{parentName:"p"},"getName()")," method in\nthe product because the name needs to be translated in the correct language. You have a ",(0,r.yg)("inlineCode",{parentName:"p"},"TranslationService")," to do that."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass Product\n{\n // ...\n\n #[Field]\n public function getId(): string\n {\n return $this->id;\n }\n\n #[Field]\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n /**\n * @Field()\n */\n public function getPrice(): ?float\n {\n return $this->price;\n }\n}\n")))),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// You need to use a service to get the name of the product in the correct language.\n$name = $translationService->getProductName($productId, $language);\n")),(0,r.yg)("p",null,"Using ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType"),", you can add an additional ",(0,r.yg)("inlineCode",{parentName:"p"},"name")," field to your product:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[ExtendType(class: Product::class)]\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n #[Field]\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\ExtendType;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n/**\n * @ExtendType(class=Product::class)\n */\nclass ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n /**\n * @Field()\n */\n public function getName(Product $product, string $language): string\n {\n return $this->translationService->getProductName($product->getId(), $language);\n }\n}\n")))),(0,r.yg)("p",null,"Let's break this sample:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[ExtendType(class: Product::class)]\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @ExtendType(class=Product::class)\n */\n")))),(0,r.yg)("p",null,"With the ",(0,r.yg)("inlineCode",{parentName:"p"},"@ExtendType")," annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," PHP class."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductType\n{\n private $translationService;\n\n public function __construct(TranslationServiceInterface $translationService)\n {\n $this->translationService = $translationService;\n }\n\n // ...\n}\n")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class must be in the types namespace. You configured this namespace when you installed GraphQLite."),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ProductType")," class is actually a ",(0,r.yg)("strong",{parentName:"li"},"service"),". You can therefore inject dependencies in it (like the ",(0,r.yg)("inlineCode",{parentName:"li"},"$translationService")," in this example)")),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Heads up!")," The ",(0,r.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,r.yg)("br",null),(0,r.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Field()\n */\npublic function getName(Product $product, string $language): string\n{\n return $this->translationService->getProductName($product->getId(), $language);\n}\n")))),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field"),' annotation is used to add the "name" field to the ',(0,r.yg)("inlineCode",{parentName:"p"},"Product")," type."),(0,r.yg)("p",null,'Take a close look at the signature. The first parameter is the "resolved object" we are working on.\nAny additional parameters are used as arguments.'),(0,r.yg)("p",null,'Using the "',(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"Type language"),'" notation, we defined a type extension for\nthe GraphQL "Product" type:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"Extend type Product {\n name(language: !String): String!\n}\n")),(0,r.yg)("div",{class:"alert alert--success"},"Type extension is a very powerful tool. Use it to add fields that needs to be computed from services not available in the entity."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c329487f.bef3f016.js b/assets/js/c329487f.4cfc14e5.js similarity index 98% rename from assets/js/c329487f.bef3f016.js rename to assets/js/c329487f.4cfc14e5.js index d1fbb5511d..90025a355b 100644 --- a/assets/js/c329487f.bef3f016.js +++ b/assets/js/c329487f.4cfc14e5.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9249],{29974:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var t=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"changelog",title:"Changelog",sidebar_label:"Changelog",original_id:"changelog"},o=void 0,l={unversionedId:"changelog",id:"version-4.0/changelog",title:"Changelog",description:"4.0",source:"@site/versioned_docs/version-4.0/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/4.0/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/CHANGELOG.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog",original_id:"changelog"},sidebar:"version-4.0/docs",previous:{title:"Semantic versioning",permalink:"/docs/4.0/semver"}},s={},p=[{value:"4.0",id:"40",level:2}],d={toc:p},g="wrapper";function u(e){let{components:a,...n}=e;return(0,i.yg)(g,(0,t.A)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"40"},"4.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("p",null,"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/error-handling"},"GraphQL errors")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException")),(0,i.yg)("li",{parentName:"ul"},'You can specify the HTTP response code to send with a given error, and the errors "extensions" section'),(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can map ",(0,i.yg)("a",{parentName:"li",href:"input-types#declaring-several-input-types-for-the-same-php-class"},"a given PHP class to several PHP input types")," (a PHP class can have several ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory")," annotations)"),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/extend_input_type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("p",null,"Symfony:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("p",null,"Laravel:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("p",null,"Internals:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9249],{29974:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var t=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"changelog",title:"Changelog",sidebar_label:"Changelog",original_id:"changelog"},o=void 0,l={unversionedId:"changelog",id:"version-4.0/changelog",title:"Changelog",description:"4.0",source:"@site/versioned_docs/version-4.0/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/4.0/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/CHANGELOG.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog",original_id:"changelog"},sidebar:"version-4.0/docs",previous:{title:"Semantic versioning",permalink:"/docs/4.0/semver"}},s={},p=[{value:"4.0",id:"40",level:2}],d={toc:p},g="wrapper";function u(e){let{components:a,...n}=e;return(0,i.yg)(g,(0,t.A)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"40"},"4.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("p",null,"New features:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"@Type")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"@Assertion")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@Security")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"@InjectUser")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/error-handling"},"GraphQL errors")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLException")),(0,i.yg)("li",{parentName:"ul"},'You can specify the HTTP response code to send with a given error, and the errors "extensions" section'),(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can map ",(0,i.yg)("a",{parentName:"li",href:"input-types#declaring-several-input-types-for-the-same-php-class"},"a given PHP class to several PHP input types")," (a PHP class can have several ",(0,i.yg)("inlineCode",{parentName:"li"},"@Factory")," annotations)"),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/extend_input_type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"@Decorate")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("p",null,"Symfony:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("p",null,"Laravel:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("p",null,"Internals:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c3701568.6e5085a4.js b/assets/js/c3701568.23c5e54b.js similarity index 97% rename from assets/js/c3701568.6e5085a4.js rename to assets/js/c3701568.23c5e54b.js index 157968d5f3..00190d8e72 100644 --- a/assets/js/c3701568.6e5085a4.js +++ b/assets/js/c3701568.23c5e54b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1968],{32369:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>p,frontMatter:()=>o,metadata:()=>s,toc:()=>u});var n=i(58168),a=(i(96540),i(15680));i(67443);const o={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services",original_id:"autowiring"},r=void 0,s={unversionedId:"autowiring",id:"version-4.0/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-4.0/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/4.0/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/autowiring.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services",original_id:"autowiring"},sidebar:"version-4.0/docs",previous:{title:"Type mapping",permalink:"/docs/4.0/type_mapping"},next:{title:"Extending a type",permalink:"/docs/4.0/extend_type"}},l={},u=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],c={toc:u},d="wrapper";function p(e){let{components:t,...i}=e;return(0,a.yg)(d,(0,n.A)({},c,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,a.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,a.yg)("p",null,"Most of the time, your ",(0,a.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,a.yg)("h2",{id:"sample"},"Sample"),(0,a.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,a.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")),(0,a.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,a.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,a.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,a.yg)("h2",{id:"best-practices"},"Best practices"),(0,a.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,a.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,a.yg)("pre",null,(0,a.yg)("code",null," /** * @Field() */ public function getName(MyTranslator $translator): string"))),(0,a.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,a.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,a.yg)("pre",null,(0,a.yg)("code",null," /** * @Field() */ public function getName(TranslatorInterface $translator): string"))),(0,a.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,a.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,a.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')),(0,a.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,a.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,a.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,a.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,a.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,a.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,a.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,a.yg)("a",{parentName:"p",href:"extend_type"},"how to extend a type"),"."))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1968],{32369:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>p,frontMatter:()=>o,metadata:()=>s,toc:()=>u});var n=i(58168),a=(i(96540),i(15680));i(67443);const o={id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services",original_id:"autowiring"},r=void 0,s={unversionedId:"autowiring",id:"version-4.0/autowiring",title:"Autowiring services",description:"GraphQLite can automatically inject services in your fields/queries/mutations signatures.",source:"@site/versioned_docs/version-4.0/autowiring.mdx",sourceDirName:".",slug:"/autowiring",permalink:"/docs/4.0/autowiring",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/autowiring.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"autowiring",title:"Autowiring services",sidebar_label:"Autowiring services",original_id:"autowiring"},sidebar:"version-4.0/docs",previous:{title:"Type mapping",permalink:"/docs/4.0/type_mapping"},next:{title:"Extending a type",permalink:"/docs/4.0/extend_type"}},l={},u=[{value:"Sample",id:"sample",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"Fetching a service by name (discouraged!)",id:"fetching-a-service-by-name-discouraged",level:2},{value:"Alternative solution",id:"alternative-solution",level:2}],c={toc:u},d="wrapper";function p(e){let{components:t,...i}=e;return(0,a.yg)(d,(0,n.A)({},c,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"GraphQLite can automatically inject services in your fields/queries/mutations signatures."),(0,a.yg)("p",null,"Some of your fields may be computed. In order to compute these fields, you might need to call a service."),(0,a.yg)("p",null,"Most of the time, your ",(0,a.yg)("inlineCode",{parentName:"p"},"@Type")," annotation will be put on a model. And models do not have access to services.\nHopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with\nthe service instance."),(0,a.yg)("h2",{id:"sample"},"Sample"),(0,a.yg)("p",null,"Let's assume you are running an international store. You have a ",(0,a.yg)("inlineCode",{parentName:"p"},"Product")," class. Each product has many names (depending\non the language of the user)."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Entities;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Autowire;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\nuse Symfony\\Component\\Translation\\TranslatorInterface;\n\n/**\n * @Type()\n */\nclass Product\n{\n // ...\n\n /**\n * @Field()\n * @Autowire(for=\"$translator\")\n */\n public function getName(TranslatorInterface $translator): string\n {\n return $translator->trans('product_name_'.$this->id);\n }\n}\n")),(0,a.yg)("p",null,"When GraphQLite queries the name, it will automatically fetch the translator service."),(0,a.yg)("div",{class:"alert alert--warning"},"As with most autowiring solutions, GraphQLite assumes that the service identifier in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will look for a service whose name is ",(0,a.yg)("code",null,"Symfony\\Component\\Translation\\TranslatorInterface"),"."),(0,a.yg)("h2",{id:"best-practices"},"Best practices"),(0,a.yg)("p",null,"It is a good idea to refrain from type-hinting on concrete implementations.\nMost often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain\nwith a particular service implementation. This makes your code tightly coupled and less testable."),(0,a.yg)("div",{class:"alert alert--danger"},"Please don't do that:",(0,a.yg)("pre",null,(0,a.yg)("code",null," /** * @Field() */ public function getName(MyTranslator $translator): string"))),(0,a.yg)("p",null,"Instead, be sure to type-hint against an interface."),(0,a.yg)("div",{class:"alert alert--success"},"Do this instead:",(0,a.yg)("pre",null,(0,a.yg)("code",null," /** * @Field() */ public function getName(TranslatorInterface $translator): string"))),(0,a.yg)("p",null,"By type-hinting against an interface, your code remains testable and is decoupled from the service implementation."),(0,a.yg)("h2",{id:"fetching-a-service-by-name-discouraged"},"Fetching a service by name (discouraged!)"),(0,a.yg)("p",null,"Optionally, you can specify the identifier of the service you want to fetch from the controller:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Autowire(for="$translator", identifier="translator")\n */\n')),(0,a.yg)("div",{class:"alert alert--danger"},"While GraphQLite offers the possibility to specify the name of the service to be autowired, we would like to emphasize that this is ",(0,a.yg)("strong",null,"highly discouraged"),'. Hard-coding a container identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an anti-pattern. Please refrain from doing this as much as possible.'),(0,a.yg)("h2",{id:"alternative-solution"},"Alternative solution"),(0,a.yg)("p",null,"You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)"),(0,a.yg)("li",{parentName:"ul"},"You do not want to inject a service in a domain object"),(0,a.yg)("li",{parentName:"ul"},"You simply do not like the magic of injecting services in a method signature")),(0,a.yg)("p",null,"If you do not want to use autowiring and if you still need to access services to compute a field, please read on\nthe next chapter to learn ",(0,a.yg)("a",{parentName:"p",href:"extend_type"},"how to extend a type"),"."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c4d37b36.c7abd8e8.js b/assets/js/c4d37b36.850e299e.js similarity index 96% rename from assets/js/c4d37b36.c7abd8e8.js rename to assets/js/c4d37b36.850e299e.js index d17bfc3e2e..40c9d5ed18 100644 --- a/assets/js/c4d37b36.c7abd8e8.js +++ b/assets/js/c4d37b36.850e299e.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1141],{71680:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>g,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var t=n(58168),r=(n(96540),n(15680));n(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-6.1/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.1/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/6.1/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/argument-resolving.md",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"docs",previous:{title:"Custom annotations",permalink:"/docs/6.1/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/6.1/extend-input-type"}},s={},p=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},d="wrapper";function g(e){let{components:a,...n}=e;return(0,r.yg)(d,(0,t.A)({},m,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Autowire")," annotation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1141],{71680:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var t=n(58168),r=(n(96540),n(15680));n(67443);const i={id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},o=void 0,l={unversionedId:"argument-resolving",id:"version-6.1/argument-resolving",title:"Extending argument resolving",description:"Available in GraphQLite 4.0+",source:"@site/versioned_docs/version-6.1/argument-resolving.md",sourceDirName:".",slug:"/argument-resolving",permalink:"/docs/6.1/argument-resolving",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/argument-resolving.md",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"argument-resolving",title:"Extending argument resolving",sidebar_label:"Custom argument resolving"},sidebar:"docs",previous:{title:"Custom annotations",permalink:"/docs/6.1/field-middlewares"},next:{title:"Extending an input type",permalink:"/docs/6.1/extend-input-type"}},s={},p=[{value:"Annotations parsing",id:"annotations-parsing",level:2},{value:"Writing the parameter middleware",id:"writing-the-parameter-middleware",level:2},{value:"Registering a parameter middleware",id:"registering-a-parameter-middleware",level:2}],m={toc:p},g="wrapper";function d(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},m,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("p",null,"Using a ",(0,r.yg)("strong",{parentName:"p"},"parameter middleware"),", you can hook into the argument resolution of field/query/mutation/factory."),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to alter the way arguments are injected in a method or if you want to alter the way input types are imported (for instance if you want to add a validation step)"),(0,r.yg)("p",null,"As an example, GraphQLite uses ",(0,r.yg)("em",{parentName:"p"},"parameter middlewares")," internally to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject the Webonyx GraphQL resolution object when you type-hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object. For instance:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @return Product[]\n */\n#[Query]\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",{parentName:"li"},"In the query above, the ",(0,r.yg)("inlineCode",{parentName:"p"},"$info")," argument is filled with the Webonyx ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," class thanks to the\n",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler parameter middleware")))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Inject a service from the container when you use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Autowire")," annotation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},"Perform validation with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation (in Laravel package)"))),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameter middlewares")),(0,r.yg)("img",{src:"/img/parameter_middleware.svg",width:"70%"}),(0,r.yg)("p",null,"Each middleware is passed number of objects describing the parameter:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a PHP ",(0,r.yg)("inlineCode",{parentName:"li"},"ReflectionParameter")," object representing the parameter being manipulated"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\DocBlock")," instance (useful to analyze the ",(0,r.yg)("inlineCode",{parentName:"li"},"@param")," comment if any)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"phpDocumentor\\Reflection\\Type")," instance (useful to analyze the type if the argument)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Annotations\\ParameterAnnotations")," instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)"),(0,r.yg)("li",{parentName:"ul"},"a ",(0,r.yg)("inlineCode",{parentName:"li"},"$next")," handler to pass the argument resolving to the next middleware.")),(0,r.yg)("p",null,"Parameter resolution is done in 2 passes."),(0,r.yg)("p",null,"On the first pass, middlewares are traversed. They must return a ",(0,r.yg)("inlineCode",{parentName:"p"},"TheCodingMachine\\GraphQLite\\Parameters\\ParameterInterface")," (an object that does the actual resolving)."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface ParameterMiddlewareInterface\n{\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;\n}\n")),(0,r.yg)("p",null,"Then, resolution actually happen by executing the resolver (this is the second pass)."),(0,r.yg)("h2",{id:"annotations-parsing"},"Annotations parsing"),(0,r.yg)("p",null,"If you plan to use annotations while resolving arguments, your annotation should extend the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterAnnotationInterface"))),(0,r.yg)("p",null,"For instance, if we want GraphQLite to inject a service in an argument, we can use ",(0,r.yg)("inlineCode",{parentName:"p"},'@Autowire(for="myService")'),"."),(0,r.yg)("p",null,"For PHP 8 attributes, we only need to put declare the annotation can target parameters: ",(0,r.yg)("inlineCode",{parentName:"p"},"#[Attribute(Attribute::TARGET_PARAMETER)]"),"."),(0,r.yg)("p",null,"The annotation looks like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use Attribute;\n\n/**\n * Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.\n *\n * @Annotation\n */\n#[Attribute(Attribute::TARGET_PARAMETER)]\nclass Autowire implements ParameterAnnotationInterface\n{\n /**\n * @var string\n */\n public $for;\n\n /**\n * The getTarget method must return the name of the argument\n */\n public function getTarget(): string\n {\n return $this->for;\n }\n}\n")),(0,r.yg)("h2",{id:"writing-the-parameter-middleware"},"Writing the parameter middleware"),(0,r.yg)("p",null,"The middleware purpose is to analyze a parameter and decide whether or not it can handle it."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter middleware class"',title:'"Parameter',middleware:!0,'class"':!0},"class ContainerParameterHandler implements ParameterMiddlewareInterface\n{\n /** @var ContainerInterface */\n private $container;\n\n public function __construct(ContainerInterface $container)\n {\n $this->container = $container;\n }\n\n public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface\n {\n // The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface\n $autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);\n\n if ($autowire === null) {\n // If there are no annotation, this middleware cannot handle the parameter. Let's ask\n // the next middleware in the chain (using the $next object)\n return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);\n }\n\n // We found a @Autowire annotation, let's return a parameter resolver.\n return new ContainerParameter($this->container, $parameter->getType());\n }\n}\n")),(0,r.yg)("p",null,"The last step is to write the actual parameter resolver."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="Parameter resolver class"',title:'"Parameter',resolver:!0,'class"':!0},'/**\n * A parameter filled from the container.\n */\nclass ContainerParameter implements ParameterInterface\n{\n /** @var ContainerInterface */\n private $container;\n /** @var string */\n private $identifier;\n\n public function __construct(ContainerInterface $container, string $identifier)\n {\n $this->container = $container;\n $this->identifier = $identifier;\n }\n\n /**\n * The "resolver" returns the actual value that will be fed to the function.\n */\n public function resolve(?object $source, array $args, $context, ResolveInfo $info)\n {\n return $this->container->get($this->identifier);\n }\n}\n')),(0,r.yg)("h2",{id:"registering-a-parameter-middleware"},"Registering a parameter middleware"),(0,r.yg)("p",null,"The last step is to register the parameter middleware we just wrote:"),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".'))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c4f5d8e4.18e9bd0f.js b/assets/js/c4f5d8e4.e0be6be1.js similarity index 74% rename from assets/js/c4f5d8e4.18e9bd0f.js rename to assets/js/c4f5d8e4.e0be6be1.js index d6959d5041..b3aff8ae77 100644 --- a/assets/js/c4f5d8e4.18e9bd0f.js +++ b/assets/js/c4f5d8e4.e0be6be1.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2634],{30192:(e,n,l)=>{l.r(n),l.d(n,{default:()=>c});var t=l(78511),u=l(96540);const c=()=>("undefined"!=typeof window&&(window.location.href="/docs"),u.createElement(t.A,null,null))}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2634],{30192:(e,n,l)=>{l.r(n),l.d(n,{default:()=>c});var t=l(98956),u=l(96540);const c=()=>("undefined"!=typeof window&&(window.location.href="/docs"),u.createElement(t.A,null,null))}}]); \ No newline at end of file diff --git a/assets/js/c5fa393d.de7de634.js b/assets/js/c5fa393d.7442ce88.js similarity index 98% rename from assets/js/c5fa393d.de7de634.js rename to assets/js/c5fa393d.7442ce88.js index 5ee7d2cc47..ef1a3844d2 100644 --- a/assets/js/c5fa393d.de7de634.js +++ b/assets/js/c5fa393d.7442ce88.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2605],{97524:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>r,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>l});var a=t(58168),i=(t(96540),t(15680));t(67443);const o={id:"authentication_authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization",original_id:"authentication_authorization"},r=void 0,s={unversionedId:"authentication_authorization",id:"version-4.0/authentication_authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-4.0/authentication_authorization.mdx",sourceDirName:".",slug:"/authentication_authorization",permalink:"/docs/4.0/authentication_authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/authentication_authorization.mdx",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"authentication_authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization",original_id:"authentication_authorization"},sidebar:"version-4.0/docs",previous:{title:"User input validation",permalink:"/docs/4.0/validation"},next:{title:"Fine grained security",permalink:"/docs/4.0/fine-grained-security"}},u={},l=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],d={toc:l},h="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(h,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,i.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,i.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,i.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,i.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,i.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,i.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,i.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/fine-grained-security"},(0,i.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,i.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,i.yg)("br",null),"See ",(0,i.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,i.yg)("h2",{id:"logged-and-right-annotations"},(0,i.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,i.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,i.yg)("p",null,"GraphQLite exposes two annotations (",(0,i.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,i.yg)("p",null,"In the example above, the query ",(0,i.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,i.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,i.yg)("p",null,(0,i.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,i.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,i.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,i.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,i.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,i.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,i.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,i.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,i.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,i.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,i.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2605],{97524:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>r,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>l});var a=t(58168),i=(t(96540),t(15680));t(67443);const o={id:"authentication_authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization",original_id:"authentication_authorization"},r=void 0,s={unversionedId:"authentication_authorization",id:"version-4.0/authentication_authorization",title:"Authentication and authorization",description:"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields",source:"@site/versioned_docs/version-4.0/authentication_authorization.mdx",sourceDirName:".",slug:"/authentication_authorization",permalink:"/docs/4.0/authentication_authorization",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/authentication_authorization.mdx",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"authentication_authorization",title:"Authentication and authorization",sidebar_label:"Authentication and authorization",original_id:"authentication_authorization"},sidebar:"version-4.0/docs",previous:{title:"User input validation",permalink:"/docs/4.0/validation"},next:{title:"Fine grained security",permalink:"/docs/4.0/fine-grained-security"}},u={},l=[{value:"@Logged and @Right annotations",id:"logged-and-right-annotations",level:2},{value:"Not throwing errors",id:"not-throwing-errors",level:2},{value:"Injecting the current user as a parameter",id:"injecting-the-current-user-as-a-parameter",level:2},{value:"Hiding fields / queries / mutations",id:"hiding-fields--queries--mutations",level:2}],d={toc:l},h="wrapper";function g(e){let{components:n,...t}=e;return(0,i.yg)(h,(0,a.A)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields\nreserved to some users."),(0,i.yg)("p",null,"GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"based on authentication using the ",(0,i.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,i.yg)("inlineCode",{parentName:"a"},"@Logged")," annotation")," (restrict access to logged users)"),(0,i.yg)("li",{parentName:"ul"},"based on authorization using the ",(0,i.yg)("a",{parentName:"li",href:"#logged-and-right-annotations"},(0,i.yg)("inlineCode",{parentName:"a"},"@Right")," annotation")," (restrict access to logged users with certain rights)."),(0,i.yg)("li",{parentName:"ul"},"based on fine-grained authorization using the ",(0,i.yg)("a",{parentName:"li",href:"/docs/4.0/fine-grained-security"},(0,i.yg)("inlineCode",{parentName:"a"},"@Security")," annotation")," (restrict access for some given resources to some users).")),(0,i.yg)("div",{class:"alert alert--info"},"GraphQLite does not have its own security mechanism. Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.",(0,i.yg)("br",null),"See ",(0,i.yg)("a",{href:"implementing-security"},"Connecting GraphQLite to your framework's security module"),"."),(0,i.yg)("h2",{id:"logged-and-right-annotations"},(0,i.yg)("inlineCode",{parentName:"h2"},"@Logged")," and ",(0,i.yg)("inlineCode",{parentName:"h2"},"@Right")," annotations"),(0,i.yg)("p",null,"GraphQLite exposes two annotations (",(0,i.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"@Right"),") that you can use to restrict access to a resource."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\n\nclass UserController\n{\n /**\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,i.yg)("p",null,"In the example above, the query ",(0,i.yg)("inlineCode",{parentName:"p"},"users")," will only be available if the user making the query is logged AND if he\nhas the ",(0,i.yg)("inlineCode",{parentName:"p"},"CAN_VIEW_USER_LIST")," right."),(0,i.yg)("p",null,(0,i.yg)("inlineCode",{parentName:"p"},"@Logged")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"@Right")," annotations can be used next to:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,i.yg)("div",{class:"alert alert--info"},"By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails."),(0,i.yg)("h2",{id:"not-throwing-errors"},"Not throwing errors"),(0,i.yg)("p",null,"If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation."),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"@FailWith")," annotation contains the value that will be returned for users with insufficient rights."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the value returned will be "null".\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @FailWith(null)\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,i.yg)("h2",{id:"injecting-the-current-user-as-a-parameter"},"Injecting the current user as a parameter"),(0,i.yg)("p",null,"Use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation to get an instance of the current user logged in."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Query;\nuse TheCodingMachine\\GraphQLite\\Annotations\\InjectUser;\n\nclass ProductController\n{\n /**\n * @Query\n * @InjectUser(for="$user")\n * @return Product\n */\n public function product(int $id, User $user): Product\n {\n // ...\n }\n}\n')),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"@InjectUser")," annotation can be used next to:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Query")," annotations"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Mutation")," annotations"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"@Field")," annotations")),(0,i.yg)("p",null,"The object injected as the current user depends on your framework. It is in fact the object returned by the\n",(0,i.yg)("a",{parentName:"p",href:"/docs/4.0/implementing-security"},'"authentication service" configured in GraphQLite'),"."),(0,i.yg)("h2",{id:"hiding-fields--queries--mutations"},"Hiding fields / queries / mutations"),(0,i.yg)("p",null,"By default, a user analysing the GraphQL schema can see all queries/mutations/types available.\nSome will be available to him and some won't."),(0,i.yg)("p",null,"If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),\nyou can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"@HideIfUnauthorized")," annotation."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'class UserController\n{\n /**\n * If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",\n * the schema will NOT contain the "users" query at all (so trying to call the\n * "users" query will result in a GraphQL "query not found" error.\n *\n * @Query\n * @Logged\n * @Right("CAN_VIEW_USER_LIST")\n * @HideIfUnauthorized()\n * @return User[]\n */\n public function users(int $limit, int $offset): array\n {\n // ...\n }\n}\n')),(0,i.yg)("p",null,"While this is the most secured mode, it can have drawbacks when working with development tools\n(you need to be logged as admin to fetch the complete schema)."),(0,i.yg)("div",{class:"alert alert--info"},'The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.'))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c69dda99.6259f383.js b/assets/js/c69dda99.bab571e4.js similarity index 88% rename from assets/js/c69dda99.6259f383.js rename to assets/js/c69dda99.bab571e4.js index 2cae844816..6022e2ba7c 100644 --- a/assets/js/c69dda99.6259f383.js +++ b/assets/js/c69dda99.bab571e4.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6192],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=p(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),b=(()=>{const e=s??d;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==o&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,l.A)("tabs__item",y.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(f,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function T(e){const t=(0,b.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},2730:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},s=void 0,i={unversionedId:"index",id:"version-4.2/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-4.2/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/4.2/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/README.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"version-4.2/docs",next:{title:"Getting Started",permalink:"/docs/4.2/getting-started"}},c={},d=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],p={toc:d},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6192],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),l=a(20053),u=a(23104),o=a(56347),s=a(57485),i=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,o.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[u,o]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[s,i]=g({queryString:a,groupId:n}),[p,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),b=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&o(b)}),[b]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),i(e),h(e)}),[i,h,l]),tabValues:l}}var b=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:a,selectedValue:o,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==o&&(p(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},u,{className:(0,l.A)("tabs__item",y.tabItem,u?.className,{"tabs__item--active":o===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=h(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(f,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function T(e){const t=(0,b.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},2730:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>o,metadata:()=>i,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),u=a(19365);const o={id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},s=void 0,i={unversionedId:"index",id:"version-4.2/index",title:"GraphQLite",description:"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.",source:"@site/versioned_docs/version-4.2/README.mdx",sourceDirName:".",slug:"/",permalink:"/docs/4.2/",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/README.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"index",title:"GraphQLite",slug:"/",sidebar_label:"GraphQLite"},sidebar:"version-4.2/docs",next:{title:"Getting Started",permalink:"/docs/4.2/getting-started"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Basic example",id:"basic-example",level:2}],d={toc:p},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",{align:"center"},(0,r.yg)("img",{src:"https://graphqlite.thecodingmachine.io/img/logo.svg",alt:"GraphQLite logo",width:"250",height:"250"})),(0,r.yg)("p",null,"A PHP library that allows you to write your GraphQL queries in simple-to-write controllers."),(0,r.yg)("h2",{id:"features"},"Features"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Create a complete GraphQL API by simply annotating your PHP classes"),(0,r.yg)("li",{parentName:"ul"},"Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!"),(0,r.yg)("li",{parentName:"ul"},"Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!")),(0,r.yg)("h2",{id:"basic-example"},"Basic example"),(0,r.yg)("p",null,"First, declare a query in your controller:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n #[Query]\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ProductController\n{\n /**\n * @Query()\n */\n public function product(string $id): Product\n {\n // Some code that looks for a product and returns it.\n }\n}\n")))),(0,r.yg)("p",null,"Then, annotate the ",(0,r.yg)("inlineCode",{parentName:"p"},"Product")," class to declare what fields are exposed to the GraphQL API:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Product\n{\n #[Field]\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type()\n */\nclass Product\n{\n /**\n * @Field()\n */\n public function getName(): string\n {\n return $this->name;\n }\n // ...\n}\n")))),(0,r.yg)("p",null,"That's it, you're good to go! Query and enjoy!"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n product(id: 42) {\n name\n }\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c7a4caa1.d453236b.js b/assets/js/c7a4caa1.9023b5ab.js similarity index 96% rename from assets/js/c7a4caa1.d453236b.js rename to assets/js/c7a4caa1.9023b5ab.js index 283be9febb..9b13d51141 100644 --- a/assets/js/c7a4caa1.d453236b.js +++ b/assets/js/c7a4caa1.9023b5ab.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1308],{2393:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>v,frontMatter:()=>t,metadata:()=>s,toc:()=>l});var n=i(58168),a=(i(96540),i(15680));i(67443);const t={id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},o=void 0,s={unversionedId:"universal_service_providers",id:"version-3.0/universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-3.0/universal_service_providers.md",sourceDirName:".",slug:"/universal_service_providers",permalink:"/docs/3.0/universal_service_providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/universal_service_providers.md",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},sidebar:"version-3.0/docs",previous:{title:"Laravel package",permalink:"/docs/3.0/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/3.0/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function v(e){let{components:r,...i}=e;return(0,a.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,a.yg)("p",null,"If your framework is compatible with ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,a.yg)("h2",{id:"installation"},"Installation"),(0,a.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,a.yg)("h2",{id:"requirements"},"Requirements"),(0,a.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,a.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,a.yg)("p",null,"GraphQLite relies on the ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and there is a ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/phps-cans/psr7-middleware-graphql"},"PSR-15 middleware available"),"."),(0,a.yg)("h2",{id:"integration"},"Integration"),(0,a.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,a.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,a.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,a.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"composer.json")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre"},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"index.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use Simplex\\Container;\nuse TheCodingMachine\\GraphQLite\\Schema;\nuse TheCodingMachine\\SymfonyCacheServiceProvider;\nuse TheCodingMachine\\DoctrineAnnotationsServiceProvider;\nuse TheCodingMachine\\GraphQLiteServiceProvider;\n\n$container = new Container([\n new SymfonyCacheServiceProvider(),\n new DoctrineAnnotationsServiceProvider,\n new GraphQLiteServiceProvider()]);\n$container->set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n")))}v.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[1308],{2393:(e,r,i)=>{i.r(r),i.d(r,{assets:()=>p,contentTitle:()=>o,default:()=>v,frontMatter:()=>t,metadata:()=>s,toc:()=>l});var n=i(58168),a=(i(96540),i(15680));i(67443);const t={id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},o=void 0,s={unversionedId:"universal_service_providers",id:"version-3.0/universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",description:"container-interop/service-provider is an experimental project",source:"@site/versioned_docs/version-3.0/universal_service_providers.md",sourceDirName:".",slug:"/universal_service_providers",permalink:"/docs/3.0/universal_service_providers",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/universal_service_providers.md",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"universal_service_providers",title:"Getting started with a framework compatible with container-interop/service-provider",sidebar_label:"Universal service providers",original_id:"universal_service_providers"},sidebar:"version-3.0/docs",previous:{title:"Laravel package",permalink:"/docs/3.0/laravel-package"},next:{title:"Other frameworks / No framework",permalink:"/docs/3.0/other-frameworks"}},p={},l=[{value:"Installation",id:"installation",level:2},{value:"Requirements",id:"requirements",level:2},{value:"Integration",id:"integration",level:2},{value:"Sample usage",id:"sample-usage",level:2}],c={toc:l},d="wrapper";function v(e){let{components:r,...i}=e;return(0,a.yg)(d,(0,n.A)({},c,i,{components:r,mdxType:"MDXLayout"}),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider")," is an experimental project\naiming to bring interoperability between framework module systems."),(0,a.yg)("p",null,"If your framework is compatible with ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/container-interop/service-provider/"},"container-interop/service-provider"),",\nGraphQLite comes with a service provider that you can leverage."),(0,a.yg)("h2",{id:"installation"},"Installation"),(0,a.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-universal-service-provider\n")),(0,a.yg)("h2",{id:"requirements"},"Requirements"),(0,a.yg)("p",null,"In order to bootstrap GraphQLite, you will need:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"A PSR-16 cache")),(0,a.yg)("p",null,"Additionally, you will have to route the HTTP requests to the underlying GraphQL library."),(0,a.yg)("p",null,"GraphQLite relies on the ",(0,a.yg)("a",{parentName:"p",href:"http://webonyx.github.io/graphql-php/"},"webonyx/graphql-php")," library internally.\nThis library plays well with PSR-7 requests and there is a ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/phps-cans/psr7-middleware-graphql"},"PSR-15 middleware available"),"."),(0,a.yg)("h2",{id:"integration"},"Integration"),(0,a.yg)("p",null,"Webonyx/graphql-php library requires a ",(0,a.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/schema/"},"Schema")," in order to resolve\nGraphQL queries. The service provider provides this ",(0,a.yg)("inlineCode",{parentName:"p"},"Schema")," class."),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-universal-service-provider"},"Checkout the the service-provider documentation")),(0,a.yg)("h2",{id:"sample-usage"},"Sample usage"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"composer.json")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre"},'{\n "require": {\n "mnapoli/simplex": "^0.5",\n "thecodingmachine/graphqlite-universal-service-provider": "^3",\n "thecodingmachine/symfony-cache-universal-module": "^1"\n },\n "minimum-stability": "dev",\n "prefer-stable": true\n}\n')),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"index.php")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-php"},"use Simplex\\Container;\nuse TheCodingMachine\\GraphQLite\\Schema;\nuse TheCodingMachine\\SymfonyCacheServiceProvider;\nuse TheCodingMachine\\DoctrineAnnotationsServiceProvider;\nuse TheCodingMachine\\GraphQLiteServiceProvider;\n\n$container = new Container([\n new SymfonyCacheServiceProvider(),\n new DoctrineAnnotationsServiceProvider,\n new GraphQLiteServiceProvider()]);\n$container->set('graphqlite.namespace.types', ['App\\\\Types']);\n$container->set('graphqlite.namespace.controllers', ['App\\\\Controllers']);\n\n$schema = $container->get(Schema::class);\n")))}v.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c7e7ae18.458cd986.js b/assets/js/c7e7ae18.7aac8686.js similarity index 99% rename from assets/js/c7e7ae18.458cd986.js rename to assets/js/c7e7ae18.7aac8686.js index 8541eece86..156defc334 100644 --- a/assets/js/c7e7ae18.458cd986.js +++ b/assets/js/c7e7ae18.7aac8686.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[152],{79070:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>o,default:()=>y,frontMatter:()=>r,metadata:()=>l,toc:()=>u});var a=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"input-types",title:"Input types",sidebar_label:"Input types"},o=void 0,l={unversionedId:"input-types",id:"input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/docs/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/next/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/input-types.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"docs",previous:{title:"External type declaration",permalink:"/docs/next/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/next/inheritance-interfaces"}},p={},u=[{value:"#[Input] Attribute",id:"input-attribute",level:2},{value:"Multiple Input Types from the same class",id:"multiple-input-types-from-the-same-class",level:3},{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3}],s={toc:u},d="wrapper";function y(e){let{components:t,...n}=e;return(0,i.yg)(d,(0,a.A)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,i.yg)("p",null,"Your GraphQL query might look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,i.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,i.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,i.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,i.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,i.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,i.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using the ",(0,i.yg)("a",{parentName:"p",href:"input-attribute"},(0,i.yg)("inlineCode",{parentName:"a"},"#[Input]")," attribute")," or a ",(0,i.yg)("a",{parentName:"p",href:"factory"},"Factory method"),"."),(0,i.yg)("h2",{id:"input-attribute"},"#","[","Input","]"," Attribute"),(0,i.yg)("p",null,"Using the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute, we can transform the ",(0,i.yg)("inlineCode",{parentName:"p"},"Location")," class, in the example above, into an input type. Just add the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute to the corresponding properties:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private ?string $name = null;\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function setName(string $name): void\n {\n $this->name = $name;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,i.yg)("p",null,"Now if you call the ",(0,i.yg)("inlineCode",{parentName:"p"},"getCities")," query, from the controller in the first example, the ",(0,i.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with the user provided, ",(0,i.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,i.yg)("inlineCode",{parentName:"p"},"longitude")," properties, and passed to the controller as a parameter."),(0,i.yg)("p",null,"There are some important things to notice:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Field]")," attribute is recognized on properties for Input Type, as well as setters."),(0,i.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,i.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort - no constructor required."),(0,i.yg)("li",{parentName:"ul"},"For private or protected properties implemented, a public setter is required (if they are not set via the constructor). For example ",(0,i.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),". You can also put the ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Field]")," attribute on the setter, instead of the property, allowing you to have use many other attributes (",(0,i.yg)("inlineCode",{parentName:"li"},"Security"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"Autowire"),", etc.)."))),(0,i.yg)("li",{parentName:"ul"},"For validation of these Input Types, see the ",(0,i.yg)("a",{parentName:"li",href:"validation#custom-inputtype-validation"},"Custom InputType Validation section"),"."),(0,i.yg)("li",{parentName:"ul"},"It's advised to use the ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Input]")," attribute on DTO style input type objects and not directly on your model objects. Using it on your model objects can cause coupling in undesirable ways.")),(0,i.yg)("h3",{id:"multiple-input-types-from-the-same-class"},"Multiple Input Types from the same class"),(0,i.yg)("p",null,"Simple usage of the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]"),' attribute on a class creates a GraphQL input named by class name + "Input" suffix if a class name does not end with it already. Ex. ',(0,i.yg)("inlineCode",{parentName:"p"},"LocationInput")," for ",(0,i.yg)("inlineCode",{parentName:"p"},"Location")," class."),(0,i.yg)("p",null,"You can add multiple ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attributed to the same class, give them different names and link different fields.\nConsider the following example:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n protected ?int $age;\n\n\n #[Field]\n public function setAge(?int $age): void\n {\n $this->age = $age;\n }\n}\n")),(0,i.yg)("p",null,"There are 2 input types added to the ",(0,i.yg)("inlineCode",{parentName:"p"},"UserInput")," class: ",(0,i.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,i.yg)("li",{parentName:"ul"},"Field ",(0,i.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,i.yg)("li",{parentName:"ul"},"Field ",(0,i.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,i.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,i.yg)("li",{parentName:"ul"},"Field ",(0,i.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,i.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,i.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,i.yg)("li",{parentName:"ul"},"Field ",(0,i.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,i.yg)("p",null,"Note that ",(0,i.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,i.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,i.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,i.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 7 they will be set to ",(0,i.yg)("inlineCode",{parentName:"p"},"null"),", while in PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."),(0,i.yg)("h2",{id:"factory"},"Factory"),(0,i.yg)("p",null,"A ",(0,i.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,i.yg)("p",null,"Here is an example of factory:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory attribute will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")),(0,i.yg)("p",null,"and now, you can run query like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,i.yg)("strong",{parentName:"li"},"#","[Factory]")," attribute."),(0,i.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,i.yg)("p",null,"A few important things to notice:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,i.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,i.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,i.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,i.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")),(0,i.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the #',"[Factory]"," attribute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n")),(0,i.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,i.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,i.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,i.yg)("p",null,"You can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[UseInputType]")," attribute to force an input type of a parameter."),(0,i.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')),(0,i.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,i.yg)("small",null,"Available in GraphQLite 4.0+"),(0,i.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,i.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,i.yg)("inlineCode",{parentName:"p"},"#[UseInputType]")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Factory]")," attribute to achieve your goal."),(0,i.yg)("p",null,"Here is an annotated sample:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')),(0,i.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,i.yg)("small",null,"Available in GraphQLite 4.0+"),(0,i.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,i.yg)("p",null,"Image your ",(0,i.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,i.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n")),(0,i.yg)("p",null,"With the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[HideParameter]")," attribute, you can choose to remove from the GraphQL schema any argument."),(0,i.yg)("p",null,"To be able to hide an argument, the argument must have a default value."))}y.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[152],{79070:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>o,default:()=>y,frontMatter:()=>r,metadata:()=>l,toc:()=>u});var a=n(58168),i=(n(96540),n(15680));n(67443);const r={id:"input-types",title:"Input types",sidebar_label:"Input types"},o=void 0,l={unversionedId:"input-types",id:"input-types",title:"Input types",description:"Let's assume you are developing an API that returns a list of cities around a location.",source:"@site/docs/input-types.mdx",sourceDirName:".",slug:"/input-types",permalink:"/docs/next/input-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/input-types.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"input-types",title:"Input types",sidebar_label:"Input types"},sidebar:"docs",previous:{title:"External type declaration",permalink:"/docs/next/external-type-declaration"},next:{title:"Inheritance and interfaces",permalink:"/docs/next/inheritance-interfaces"}},p={},u=[{value:"#[Input] Attribute",id:"input-attribute",level:2},{value:"Multiple Input Types from the same class",id:"multiple-input-types-from-the-same-class",level:3},{value:"Factory",id:"factory",level:2},{value:"Specifying the input type name",id:"specifying-the-input-type-name",level:3},{value:"Forcing an input type",id:"forcing-an-input-type",level:3},{value:"Declaring several input types for the same PHP class",id:"declaring-several-input-types-for-the-same-php-class",level:3},{value:"Ignoring some parameters",id:"ignoring-some-parameters",level:3}],s={toc:u},d="wrapper";function y(e){let{components:t,...n}=e;return(0,i.yg)(d,(0,a.A)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"Let's assume you are developing an API that returns a list of cities around a location."),(0,i.yg)("p",null,"Your GraphQL query might look like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return City[]\n */\n #[Query]\n public function getCities(Location $location, float $radius): array\n {\n // Some code that returns an array of cities.\n }\n}\n\n// Class Location is a simple value-object.\nclass Location\n{\n private $latitude;\n private $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,i.yg)("p",null,"If you try to run this code, you will get the following error:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre"},'CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.\n')),(0,i.yg)("p",null,"You are running into this error because GraphQLite does not know how to handle the ",(0,i.yg)("inlineCode",{parentName:"p"},"Location")," object."),(0,i.yg)("p",null,"In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an ",(0,i.yg)("strong",{parentName:"p"},"Input Type"),"."),(0,i.yg)("p",null,"There are two ways for declaring that type, in GraphQLite: using the ",(0,i.yg)("a",{parentName:"p",href:"input-attribute"},(0,i.yg)("inlineCode",{parentName:"a"},"#[Input]")," attribute")," or a ",(0,i.yg)("a",{parentName:"p",href:"factory"},"Factory method"),"."),(0,i.yg)("h2",{id:"input-attribute"},"#","[","Input","]"," Attribute"),(0,i.yg)("p",null,"Using the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attribute, we can transform the ",(0,i.yg)("inlineCode",{parentName:"p"},"Location")," class, in the example above, into an input type. Just add the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Field]")," attribute to the corresponding properties:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Input]\nclass Location\n{\n\n #[Field]\n private ?string $name = null;\n\n #[Field]\n private float $latitude;\n\n #[Field]\n private float $longitude;\n\n public function __construct(float $latitude, float $longitude)\n {\n $this->latitude = $latitude;\n $this->longitude = $longitude;\n }\n\n public function setName(string $name): void\n {\n $this->name = $name;\n }\n\n public function getLatitude(): float\n {\n return $this->latitude;\n }\n\n public function getLongitude(): float\n {\n return $this->longitude;\n }\n}\n")),(0,i.yg)("p",null,"Now if you call the ",(0,i.yg)("inlineCode",{parentName:"p"},"getCities")," query, from the controller in the first example, the ",(0,i.yg)("inlineCode",{parentName:"p"},"Location")," object will be automatically instantiated with the user provided, ",(0,i.yg)("inlineCode",{parentName:"p"},"latitude")," / ",(0,i.yg)("inlineCode",{parentName:"p"},"longitude")," properties, and passed to the controller as a parameter."),(0,i.yg)("p",null,"There are some important things to notice:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Field]")," attribute is recognized on properties for Input Type, as well as setters."),(0,i.yg)("li",{parentName:"ul"},"There are 3 ways for fields to be resolved:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Via constructor if corresponding properties are mentioned as parameters with the same names - exactly as in the example above."),(0,i.yg)("li",{parentName:"ul"},"If properties are public, they will be just set without any additional effort - no constructor required."),(0,i.yg)("li",{parentName:"ul"},"For private or protected properties implemented, a public setter is required (if they are not set via the constructor). For example ",(0,i.yg)("inlineCode",{parentName:"li"},"setLatitude(float $latitude)"),". You can also put the ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Field]")," attribute on the setter, instead of the property, allowing you to have use many other attributes (",(0,i.yg)("inlineCode",{parentName:"li"},"Security"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"Right"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"Autowire"),", etc.)."))),(0,i.yg)("li",{parentName:"ul"},"For validation of these Input Types, see the ",(0,i.yg)("a",{parentName:"li",href:"validation#custom-inputtype-validation"},"Custom InputType Validation section"),"."),(0,i.yg)("li",{parentName:"ul"},"It's advised to use the ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Input]")," attribute on DTO style input type objects and not directly on your model objects. Using it on your model objects can cause coupling in undesirable ways.")),(0,i.yg)("h3",{id:"multiple-input-types-from-the-same-class"},"Multiple Input Types from the same class"),(0,i.yg)("p",null,"Simple usage of the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]"),' attribute on a class creates a GraphQL input named by class name + "Input" suffix if a class name does not end with it already. Ex. ',(0,i.yg)("inlineCode",{parentName:"p"},"LocationInput")," for ",(0,i.yg)("inlineCode",{parentName:"p"},"Location")," class."),(0,i.yg)("p",null,"You can add multiple ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Input]")," attributed to the same class, give them different names and link different fields.\nConsider the following example:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Input(name: 'CreateUserInput', default: true)]\n#[Input(name: 'UpdateUserInput', update: true)]\nclass UserInput\n{\n\n #[Field]\n public string $username;\n\n #[Field(for: 'CreateUserInput')]\n public string $email;\n\n #[Field(for: 'CreateUserInput', inputType: 'String!')]\n #[Field(for: 'UpdateUserInput', inputType: 'String')]\n public string $password;\n\n protected ?int $age;\n\n\n #[Field]\n public function setAge(?int $age): void\n {\n $this->age = $age;\n }\n}\n")),(0,i.yg)("p",null,"There are 2 input types added to the ",(0,i.yg)("inlineCode",{parentName:"p"},"UserInput")," class: ",(0,i.yg)("inlineCode",{parentName:"p"},"CreateUserInput")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". A few notes:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input will be used by default for this class."),(0,i.yg)("li",{parentName:"ul"},"Field ",(0,i.yg)("inlineCode",{parentName:"li"},"username")," is created for both input types, and it is required because the property type is not nullable."),(0,i.yg)("li",{parentName:"ul"},"Field ",(0,i.yg)("inlineCode",{parentName:"li"},"email")," will appear only for ",(0,i.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," input."),(0,i.yg)("li",{parentName:"ul"},"Field ",(0,i.yg)("inlineCode",{parentName:"li"},"password")," will appear for both. For ",(0,i.yg)("inlineCode",{parentName:"li"},"CreateUserInput")," it'll be the required field and for ",(0,i.yg)("inlineCode",{parentName:"li"},"UpdateUserInput")," optional."),(0,i.yg)("li",{parentName:"ul"},"Field ",(0,i.yg)("inlineCode",{parentName:"li"},"age")," is optional for both input types.")),(0,i.yg)("p",null,"Note that ",(0,i.yg)("inlineCode",{parentName:"p"},"update: true")," argument for ",(0,i.yg)("inlineCode",{parentName:"p"},"UpdateUserInput"),". It should be used when input type is used for a partial update,\nIt makes all fields optional and removes all default values from thus prevents setting default values via setters or directly to public properties.\nIn example above if you use the class as ",(0,i.yg)("inlineCode",{parentName:"p"},"UpdateUserInput")," and set only ",(0,i.yg)("inlineCode",{parentName:"p"},"username")," the other ones will be ignored.\nIn PHP 7 they will be set to ",(0,i.yg)("inlineCode",{parentName:"p"},"null"),", while in PHP 8 they will be in not initialized state - this can be used as a trick\nto check if user actually passed a value for a certain field."),(0,i.yg)("h2",{id:"factory"},"Factory"),(0,i.yg)("p",null,"A ",(0,i.yg)("strong",{parentName:"p"},"Factory")," is a method that takes in parameter all the fields of the input type and return an object."),(0,i.yg)("p",null,"Here is an example of factory:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyFactory\n{\n /**\n * The Factory attribute will create automatically a LocationInput input type in GraphQL.\n */\n #[Factory]\n public function createLocation(float $latitude, float $longitude): Location\n {\n return new Location($latitude, $longitude);\n }\n}\n")),(0,i.yg)("p",null,"and now, you can run query like this:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-graphql"},"query {\n getCities(location: {\n latitude: 45.0,\n longitude: 0.0,\n },\n radius: 42)\n {\n id,\n name\n }\n}\n")),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Factories must be declared with the ",(0,i.yg)("strong",{parentName:"li"},"#","[Factory]")," attribute."),(0,i.yg)("li",{parentName:"ul"},"The parameters of the factories are the field of the GraphQL input type")),(0,i.yg)("p",null,"A few important things to notice:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.\nThis is usually already the case if you are using a container with auto-wiring capabilities"),(0,i.yg)("li",{parentName:"ul"},"We recommend that you put the factories in the same directories as the types.")),(0,i.yg)("h3",{id:"specifying-the-input-type-name"},"Specifying the input type name"),(0,i.yg)("p",null,"The GraphQL input type name is derived from the return type of the factory."),(0,i.yg)("p",null,'Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function createLocation(float $latitude, float $longitude): Location\n{\n return new Location($latitude, $longitude);\n}\n")),(0,i.yg)("p",null,'In case you want to override the input type name, you can use the "name" attribute of the #',"[Factory]"," attribute:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory(name: 'MyNewInputName', default: true)]\n")),(0,i.yg)("p",null,'Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in\nthe next chapter).'),(0,i.yg)("p",null,"Unless you want to have several factories for the same PHP class, the input type name will be completely transparent\nto you, so there is no real reason to customize it."),(0,i.yg)("h3",{id:"forcing-an-input-type"},"Forcing an input type"),(0,i.yg)("p",null,"You can use the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[UseInputType]")," attribute to force an input type of a parameter."),(0,i.yg)("p",null,'Let\'s say you want to force a parameter to be of type "ID", you can use this:'),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'#[Factory]\n#[UseInputType(for: "$id", inputType:"ID!")]\npublic function getProductById(string $id): Product\n{\n return $this->productRepository->findById($id);\n}\n')),(0,i.yg)("h3",{id:"declaring-several-input-types-for-the-same-php-class"},"Declaring several input types for the same PHP class"),(0,i.yg)("small",null,"Available in GraphQLite 4.0+"),(0,i.yg)("p",null,"There are situations where a given PHP class might use one factory or another depending on the context."),(0,i.yg)("p",null,"This is often the case when your objects map database entities.\nIn these cases, you can use combine the use of ",(0,i.yg)("inlineCode",{parentName:"p"},"#[UseInputType]")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"#[Factory]")," attribute to achieve your goal."),(0,i.yg)("p",null,"Here is an annotated sample:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * This class contains 2 factories to create Product objects.\n * The "getProduct" method is used by default to map "Product" classes.\n * The "createProduct" method will generate another input type named "CreateProductInput"\n */\nclass ProductFactory\n{\n // ...\n\n /**\n * This factory will be used by default to map "Product" classes.\n */\n #[Factory(name: "ProductRefInput", default: true)]\n public function getProduct(string $id): Product\n {\n return $this->productRepository->findById($id);\n }\n /**\n * We specify a name for this input type explicitly.\n */\n #[Factory(name: "CreateProductInput", default: false)]\n public function createProduct(string $name, string $type): Product\n {\n return new Product($name, $type);\n }\n}\n\nclass ProductController\n{\n /**\n * The "createProduct" factory will be used for this mutation.\n */\n #[Mutation]\n #[UseInputType(for: "$product", inputType: "CreateProductInput!")]\n public function saveProduct(Product $product): Product\n {\n // ...\n }\n\n /**\n * The default "getProduct" factory will be used for this query.\n *\n * @return Color[]\n */\n #[Query]\n public function availableColors(Product $product): array\n {\n // ...\n }\n}\n')),(0,i.yg)("h3",{id:"ignoring-some-parameters"},"Ignoring some parameters"),(0,i.yg)("small",null,"Available in GraphQLite 4.0+"),(0,i.yg)("p",null,"GraphQLite will automatically map all your parameters to an input type.\nBut sometimes, you might want to avoid exposing some of those parameters."),(0,i.yg)("p",null,"Image your ",(0,i.yg)("inlineCode",{parentName:"p"},"getProductById")," has an additional ",(0,i.yg)("inlineCode",{parentName:"p"},"lazyLoad")," parameter. This parameter is interesting when you call\ndirectly the function in PHP because you can have some level of optimisation on your code. But it is not something that\nyou want to expose in the GraphQL API. Let's hide it!"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"#[Factory]\npublic function getProductById(\n string $id,\n #[HideParameter]\n bool $lazyLoad = true\n ): Product\n{\n return $this->productRepository->findById($id, $lazyLoad);\n}\n")),(0,i.yg)("p",null,"With the ",(0,i.yg)("inlineCode",{parentName:"p"},"#[HideParameter]")," attribute, you can choose to remove from the GraphQL schema any argument."),(0,i.yg)("p",null,"To be able to hide an argument, the argument must have a default value."))}y.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c8bdc4df.945387d4.js b/assets/js/c8bdc4df.f87624e5.js similarity index 86% rename from assets/js/c8bdc4df.945387d4.js rename to assets/js/c8bdc4df.f87624e5.js index 1103426ca5..0f111880bf 100644 --- a/assets/js/c8bdc4df.945387d4.js +++ b/assets/js/c8bdc4df.f87624e5.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5316],{82513:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>p,contentTitle:()=>r,default:()=>h,frontMatter:()=>n,metadata:()=>l,toc:()=>s});var o=t(58168),i=(t(96540),t(15680));t(67443);const n={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},r=void 0,l={unversionedId:"file-uploads",id:"version-4.0/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-4.0/file_uploads.md",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/4.0/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/file_uploads.md",tags:[],version:"4.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},sidebar:"version-4.0/docs",previous:{title:"Prefetching records",permalink:"/docs/4.0/prefetch-method"},next:{title:"Pagination",permalink:"/docs/4.0/pagination"}},p={},s=[{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:2},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:2},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:2},{value:"Usage",id:"usage",level:2}],u={toc:s},d="wrapper";function h(e){let{components:a,...t}=e;return(0,i.yg)(d,(0,o.A)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,i.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,i.yg)("h2",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,i.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,i.yg)("h2",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,i.yg)("p",null,"In order to use this, you must first be sure that the ",(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,i.yg)("p",null,"Simply add ",(0,i.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,i.yg)("h2",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,i.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,i.yg)("h2",{id:"usage"},"Usage"),(0,i.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,i.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")),(0,i.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests."),(0,i.yg)("p",null,"See ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5316],{82513:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>s,contentTitle:()=>r,default:()=>h,frontMatter:()=>l,metadata:()=>n,toc:()=>p});var o=t(58168),i=(t(96540),t(15680));t(67443);const l={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},r=void 0,n={unversionedId:"file-uploads",id:"version-4.0/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-4.0/file_uploads.md",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/4.0/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.0/file_uploads.md",tags:[],version:"4.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads",original_id:"file-uploads"},sidebar:"version-4.0/docs",previous:{title:"Prefetching records",permalink:"/docs/4.0/prefetch-method"},next:{title:"Pagination",permalink:"/docs/4.0/pagination"}},s={},p=[{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:2},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:2},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:2},{value:"Usage",id:"usage",level:2}],u={toc:p},d="wrapper";function h(e){let{components:a,...t}=e;return(0,i.yg)(d,(0,o.A)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,i.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,i.yg)("h2",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,i.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,i.yg)("h2",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,i.yg)("p",null,"In order to use this, you must first be sure that the ",(0,i.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,i.yg)("p",null,"Simply add ",(0,i.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,i.yg)("h2",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,i.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,i.yg)("h2",{id:"usage"},"Usage"),(0,i.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,i.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")),(0,i.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests."),(0,i.yg)("p",null,"See ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c933a311.5c9464ab.js b/assets/js/c933a311.32f863e4.js similarity index 99% rename from assets/js/c933a311.5c9464ab.js rename to assets/js/c933a311.32f863e4.js index 9a3a44bf22..d5d9c746c3 100644 --- a/assets/js/c933a311.5c9464ab.js +++ b/assets/js/c933a311.32f863e4.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6151],{19365:(e,t,n)=>{n.d(t,{A:()=>r});var a=n(96540),o=n(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:n,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>N});var a=n(58168),o=n(96540),i=n(20053),r=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function d(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:o}}=e;return{value:t,label:n,attributes:a,default:o}}))}function p(e){const{values:t,children:n}=e;return(0,o.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=p(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[s,u]=h({queryString:n,groupId:a}),[d,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,c.Dv)(n);return[a,(0,o.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),g=(()=>{const e=s??d;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),m(e)}),[u,m,i]),tabValues:i}}var g=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),p=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(d(t),s(a))},y=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:y,onClick:p},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,o.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=m(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,t)),o.createElement(v,(0,a.A)({},e,t)))}function N(e){const t=(0,g.A)();return o.createElement(w,(0,a.A)({key:String(t)},e))}},39454:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>p,frontMatter:()=>i,metadata:()=>l,toc:()=>u});var a=n(58168),o=(n(96540),n(15680));n(67443),n(11470),n(19365);const i={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},r=void 0,l={unversionedId:"external-type-declaration",id:"version-6.1/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-6.1/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/6.1/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/external-type-declaration.mdx",tags:[],version:"6.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"docs",previous:{title:"Extending a type",permalink:"/docs/6.1/extend-type"},next:{title:"Input types",permalink:"/docs/6.1/input-types"}},s={},u=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],c={toc:u},d="wrapper";function p(e){let{components:t,...n}=e;return(0,o.yg)(d,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n')),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),").\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object.\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/6.1/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/6.1/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[6151],{19365:(e,t,n)=>{n.d(t,{A:()=>r});var a=n(96540),o=n(20053);const i={tabItem:"tabItem_Ymn6"};function r(e){let{children:t,hidden:n,className:r}=e;return a.createElement("div",{role:"tabpanel",className:(0,o.A)(i.tabItem,r),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>N});var a=n(58168),o=n(96540),i=n(20053),r=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function d(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:o}}=e;return{value:t,label:n,attributes:a,default:o}}))}function p(e){const{values:t,children:n}=e;return(0,o.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function y(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),i=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(i),(0,o.useCallback)((e=>{if(!i)return;const t=new URLSearchParams(a.location.search);t.set(i,e),a.replace({...a.location,search:t.toString()})}),[i,a])]}function m(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,i=p(e),[r,l]=(0,o.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!y({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:i}))),[s,u]=h({queryString:n,groupId:a}),[d,m]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,i]=(0,c.Dv)(n);return[a,(0,o.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:a}),g=(()=>{const e=s??d;return y({value:e,tabValues:i})?e:null})();(0,o.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:r,selectValue:(0,o.useCallback)((e=>{if(!y({value:e,tabValues:i}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),m(e)}),[u,m,i]),tabValues:i}}var g=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,r.a_)(),p=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(d(t),s(a))},y=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,i.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:r}=e;return o.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:y,onClick:p},r,{className:(0,i.A)("tabs__item",f.tabItem,r?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===a));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},i.map(((e,t)=>(0,o.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function w(e){const t=m(e);return o.createElement("div",{className:(0,i.A)("tabs-container",f.tabList)},o.createElement(b,(0,a.A)({},e,t)),o.createElement(v,(0,a.A)({},e,t)))}function N(e){const t=(0,g.A)();return o.createElement(w,(0,a.A)({key:String(t)},e))}},39454:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>p,frontMatter:()=>i,metadata:()=>l,toc:()=>u});var a=n(58168),o=(n(96540),n(15680));n(67443),n(11470),n(19365);const i={id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},r=void 0,l={unversionedId:"external-type-declaration",id:"version-6.1/external-type-declaration",title:"External type declaration",description:"In some cases, you cannot or do not want to put an annotation on a domain class.",source:"@site/versioned_docs/version-6.1/external-type-declaration.mdx",sourceDirName:".",slug:"/external-type-declaration",permalink:"/docs/6.1/external-type-declaration",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.1/external-type-declaration.mdx",tags:[],version:"6.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"external-type-declaration",title:"External type declaration",sidebar_label:"External type declaration"},sidebar:"docs",previous:{title:"Extending a type",permalink:"/docs/6.1/extend-type"},next:{title:"Input types",permalink:"/docs/6.1/input-types"}},s={},u=[{value:"@Type annotation with the class attribute",id:"type-annotation-with-the-class-attribute",level:2},{value:"@SourceField annotation",id:"sourcefield-annotation",level:2},{value:"@MagicField annotation",id:"magicfield-annotation",level:2},{value:"Authentication and authorization",id:"authentication-and-authorization",level:3},{value:"Declaring fields dynamically (without annotations)",id:"declaring-fields-dynamically-without-annotations",level:2}],c={toc:u},d="wrapper";function p(e){let{components:t,...n}=e;return(0,o.yg)(d,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,o.yg)("p",null,"In some cases, you cannot or do not want to put an annotation on a domain class."),(0,o.yg)("p",null,"For instance:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"The class you want to annotate is part of a third party library and you cannot modify it"),(0,o.yg)("li",{parentName:"ul"},"You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer"),(0,o.yg)("li",{parentName:"ul"},"etc.")),(0,o.yg)("h2",{id:"type-annotation-with-the-class-attribute"},(0,o.yg)("inlineCode",{parentName:"h2"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"h2"},"class")," attribute"),(0,o.yg)("p",null,"GraphQLite allows you to use a ",(0,o.yg)("em",{parentName:"p"},"proxy")," class thanks to the ",(0,o.yg)("inlineCode",{parentName:"p"},"@Type")," annotation with the ",(0,o.yg)("inlineCode",{parentName:"p"},"class")," attribute:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Types;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Field;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $product): string\n {\n return $product->getId();\n }\n}\n")),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class must be in the ",(0,o.yg)("em",{parentName:"p"},"types")," namespace. You configured this namespace when you installed GraphQLite."),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"ProductType")," class is actually a ",(0,o.yg)("strong",{parentName:"p"},"service"),". You can therefore inject dependencies in it."),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!")," The ",(0,o.yg)("code",null,"ProductType")," class must exist in the container of your application and the container identifier MUST be the fully qualified class name.",(0,o.yg)("br",null),(0,o.yg)("br",null),"If you are using the Symfony bundle (or a framework with autowiring like Laravel), this is usually not an issue as the container will automatically create the controller entry if you do not explicitly declare it."),(0,o.yg)("p",null,"In methods with a ",(0,o.yg)("inlineCode",{parentName:"p"},"@Field")," annotation, the first parameter is the ",(0,o.yg)("em",{parentName:"p"},"resolved object")," we are working on. Any additional parameters are used as arguments."),(0,o.yg)("h2",{id:"sourcefield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@SourceField")," annotation"),(0,o.yg)("p",null,"If you don't want to rewrite all ",(0,o.yg)("em",{parentName:"p"},"getters")," of your base class, you may use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotation:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type(class: Product::class)]\n#[SourceField(name: "name")]\n#[SourceField(name: "price")]\nclass ProductType\n{\n}\n')),(0,o.yg)("p",null,"By doing so, you let GraphQLite know that the type exposes the ",(0,o.yg)("inlineCode",{parentName:"p"},"getName")," method of the underlying ",(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object."),(0,o.yg)("p",null,"Internally, GraphQLite will look for methods named ",(0,o.yg)("inlineCode",{parentName:"p"},"name()"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"getName()")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"isName()"),").\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("h2",{id:"magicfield-annotation"},(0,o.yg)("inlineCode",{parentName:"h2"},"@MagicField")," annotation"),(0,o.yg)("p",null,"If your object has no getters, but instead uses magic properties (using the magic ",(0,o.yg)("inlineCode",{parentName:"p"},"__get")," method), you should use the ",(0,o.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse App\\Entities\\Product;\n\n#[Type]\n#[MagicField(name: "name", outputType: "String!")]\n#[MagicField(name: "price", outputType: "Float")]\nclass ProductType\n{\n public function __get(string $property) {\n // return some magic property\n }\n}\n')),(0,o.yg)("p",null,'By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying ',(0,o.yg)("inlineCode",{parentName:"p"},"Product")," object.\nYou can set different name to look for with ",(0,o.yg)("inlineCode",{parentName:"p"},"sourceName")," attribute."),(0,o.yg)("p",null,"This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties."),(0,o.yg)("p",null,"Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type\nof each property manually."),(0,o.yg)("h3",{id:"authentication-and-authorization"},"Authentication and authorization"),(0,o.yg)("p",null,'You may also check for logged users or users with a specific right using the "annotations" property.'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Annotations\\Type;\nuse TheCodingMachine\\GraphQLite\\Annotations\\SourceField;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Logged;\nuse TheCodingMachine\\GraphQLite\\Annotations\\Right;\nuse TheCodingMachine\\GraphQLite\\Annotations\\FailWith;\nuse App\\Entities\\Product;\n\n/**\n * @Type(class=Product::class)\n * @SourceField(name="name")\n * @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))\n */\nclass ProductType extends AbstractAnnotatedObjectType\n{\n}\n')),(0,o.yg)("p",null,"Any annotations described in the ",(0,o.yg)("a",{parentName:"p",href:"/docs/6.1/authentication-authorization"},"Authentication and authorization page"),", or any annotation this is actually a ",(0,o.yg)("a",{parentName:"p",href:"/docs/6.1/field-middlewares"},'"field middleware"')," can be used in the ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField"),' "annotations" attribute.'),(0,o.yg)("div",{class:"alert alert--warning"},(0,o.yg)("strong",null,"Heads up!"),' The "annotation" attribute in @SourceField and @MagicField is only available as a ',(0,o.yg)("strong",null,"Doctrine annotations"),". You cannot use it in PHP 8 attributes (because PHP 8 attributes cannot be nested)"),(0,o.yg)("h2",{id:"declaring-fields-dynamically-without-annotations"},"Declaring fields dynamically (without annotations)"),(0,o.yg)("p",null,"In some very particular cases, you might not know exactly the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," annotations at development time.\nIf you need to decide the list of ",(0,o.yg)("inlineCode",{parentName:"p"},"@SourceField")," at runtime, you can implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"FromSourceFieldsInterface"),":"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\FromSourceFieldsInterface;\n\n#[Type(class: Product::class)]\nclass ProductType implements FromSourceFieldsInterface\n{\n /**\n * Dynamically returns the array of source fields\n * to be fetched from the original object.\n *\n * @return SourceFieldInterface[]\n */\n public function getSourceFields(): array\n {\n // You may want to enable fields conditionally based on feature flags...\n if (ENABLE_STATUS_GLOBALLY) {\n return [\n new SourceField(['name'=>'status', 'logged'=>true]),\n ];\n } else {\n return [];\n }\n }\n}\n")))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c953ec08.c0dba9ed.js b/assets/js/c953ec08.040e054c.js similarity index 98% rename from assets/js/c953ec08.c0dba9ed.js rename to assets/js/c953ec08.040e054c.js index 23002155e9..76d805ab40 100644 --- a/assets/js/c953ec08.c0dba9ed.js +++ b/assets/js/c953ec08.040e054c.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5011],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const o={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),o=a(20053),u=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=p(e),[u,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,o]),tabValues:o}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,o.A)("tabs__item",v.tabItem,u?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function T(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},74229:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),o=(a(67443),a(11470)),u=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,i={unversionedId:"mutations",id:"version-5.0/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-5.0/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/5.0/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/mutations.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"version-5.0/docs",previous:{title:"Queries",permalink:"/docs/5.0/queries"},next:{title:"Type mapping",permalink:"/docs/5.0/type-mapping"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5011],{19365:(e,t,a)=>{a.d(t,{A:()=>u});var n=a(96540),r=a(20053);const o={tabItem:"tabItem_Ymn6"};function u(e){let{children:t,hidden:a,className:u}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,u),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>T});var n=a(58168),r=a(96540),o=a(20053),u=a(23104),l=a(56347),s=a(57485),i=a(31682),c=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function p(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,i.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function b(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,o=p(e),[u,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,i]=b({queryString:a,groupId:n}),[d,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&o.set(e)}),[a,o])]}({groupId:n}),f=(()=>{const e=s??d;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{f&&l(f)}),[f]);return{selectedValue:u,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),i(e),h(e)}),[i,h,o]),tabValues:o}}var f=a(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:i}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,u.a_)(),p=e=>{const t=e.currentTarget,a=c.indexOf(t),n=i[a].value;n!==l&&(d(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":a},t)},i.map((e=>{let{value:t,label:a,attributes:u}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:p},u,{className:(0,o.A)("tabs__item",v.tabItem,u?.className,{"tabs__item--active":l===t})}),a??t)})))}function y(e){let{lazy:t,children:a,selectedValue:n}=e;const o=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function A(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",v.tabList)},r.createElement(g,(0,n.A)({},e,t)),r.createElement(y,(0,n.A)({},e,t)))}function T(e){const t=(0,f.A)();return r.createElement(A,(0,n.A)({key:String(t)},e))}},74229:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>b,frontMatter:()=>l,metadata:()=>i,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),o=(a(67443),a(11470)),u=a(19365);const l={id:"mutations",title:"Mutations",sidebar_label:"Mutations"},s=void 0,i={unversionedId:"mutations",id:"version-5.0/mutations",title:"Mutations",description:"In GraphQLite, mutations are created like queries.",source:"@site/versioned_docs/version-5.0/mutations.mdx",sourceDirName:".",slug:"/mutations",permalink:"/docs/5.0/mutations",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/mutations.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"mutations",title:"Mutations",sidebar_label:"Mutations"},sidebar:"version-5.0/docs",previous:{title:"Queries",permalink:"/docs/5.0/queries"},next:{title:"Type mapping",permalink:"/docs/5.0/type-mapping"}},c={},d=[],p={toc:d},m="wrapper";function b(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In GraphQLite, mutations are created ",(0,r.yg)("a",{parentName:"p",href:"/docs/5.0/queries"},"like queries"),"."),(0,r.yg)("p",null,"To create a mutation, you must annotate a method in a controller with the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Mutation")," annotation."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n #[Mutation]\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n"))),(0,r.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"namespace App\\Controller;\n\nuse TheCodingMachine\\GraphQLite\\Annotations\\Mutation;\n\nclass ProductController\n{\n /**\n * @Mutation\n */\n public function saveProduct(int $id, string $name, ?float $price = null): Product\n {\n // Some code that saves a product.\n }\n}\n")))))}b.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ca36df4d.aeb4098b.js b/assets/js/ca36df4d.d8c20347.js similarity index 99% rename from assets/js/ca36df4d.aeb4098b.js rename to assets/js/ca36df4d.d8c20347.js index f5d012efc3..1d9463fc71 100644 --- a/assets/js/ca36df4d.aeb4098b.js +++ b/assets/js/ca36df4d.d8c20347.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2674],{81316:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>r,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var t=a(58168),i=(a(96540),a(15680));a(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"changelog",title:"Changelog",description:"7.0.0",source:"@site/docs/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/next/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/CHANGELOG.md",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"docs",previous:{title:"Semantic versioning",permalink:"/docs/next/semver"}},p={},s=[{value:"7.0.0",id:"700",level:2},{value:"Breaking Changes",id:"breaking-changes",level:3},{value:"New Features",id:"new-features",level:3},{value:"Improvements",id:"improvements",level:3},{value:"Minor Changes",id:"minor-changes",level:3},{value:"6.2.3",id:"623",level:2},{value:"6.2.2",id:"622",level:2},{value:"6.2.1",id:"621",level:2},{value:"6.2.0",id:"620",level:2},{value:"6.1.0",id:"610",level:2},{value:"Breaking Changes",id:"breaking-changes-1",level:3},{value:"Improvements",id:"improvements-1",level:3},{value:"5.0.0",id:"500",level:2},{value:"Dependencies",id:"dependencies",level:3},{value:"4.3.0",id:"430",level:2},{value:"Breaking change",id:"breaking-change",level:3},{value:"Minor changes",id:"minor-changes-1",level:3},{value:"4.2.0",id:"420",level:2},{value:"Breaking change",id:"breaking-change-1",level:3},{value:"New features",id:"new-features-1",level:3},{value:"4.1.0",id:"410",level:2},{value:"Breaking change",id:"breaking-change-2",level:3},{value:"New features",id:"new-features-2",level:3},{value:"Minor changes",id:"minor-changes-2",level:3},{value:"Miscellaneous",id:"miscellaneous",level:3},{value:"4.0.0",id:"400",level:2},{value:"New features",id:"new-features-3",level:3},{value:"Symfony",id:"symfony",level:3},{value:"Laravel",id:"laravel",level:3},{value:"Internals",id:"internals",level:3}],d={toc:s},u="wrapper";function g(e){let{components:n,...a}=e;return(0,i.yg)(u,(0,t.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"700"},"7.0.0"),(0,i.yg)("h3",{id:"breaking-changes"},"Breaking Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#664 Replaces ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/thecodingmachine/class-explorer"},"thecodingmachine/class-explorer")," with ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/alekitto/class-finder"},"kcs/class-finder")," resulting in the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory::setClassNameMapper")," being renamed to ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory::setFinder"),". This now expects an instance of ",(0,i.yg)("inlineCode",{parentName:"li"},"Kcs\\ClassFinder\\Finder")," instead of ",(0,i.yg)("inlineCode",{parentName:"li"},"Kcs\\ClassFinder\\Finder\\FinderInterface"),". @fogrye")),(0,i.yg)("h3",{id:"new-features"},"New Features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#649 Adds support for ",(0,i.yg)("inlineCode",{parentName:"li"},"subscription")," operations. @oojacoboo"),(0,i.yg)("li",{parentName:"ul"},"#612 Automatic query complexity analysis. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#611 Automatic persisted queries. @oprypkhantc")),(0,i.yg)("h3",{id:"improvements"},"Improvements"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#658 Improves on prefetching for nested fields. @grynchuk"),(0,i.yg)("li",{parentName:"ul"},"#646 Improves exception handling during schema parsing. @fogrye"),(0,i.yg)("li",{parentName:"ul"},"#636 Allows the use of middleware on construtor params/fields. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#623 Improves support for description arguments on types/fields. @downace"),(0,i.yg)("li",{parentName:"ul"},"#628 Properly handles ",(0,i.yg)("inlineCode",{parentName:"li"},"@param")," annotations for generics support on field annotated constructor arguments. @oojacoboo"),(0,i.yg)("li",{parentName:"ul"},"#584 Immutability improvements across the codebase. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#588 Prefetch improvements. @oprpkhantc"),(0,i.yg)("li",{parentName:"ul"},"#606 Adds support for phpdoc descriptions and deprecation annotations on native enums. @mdoelker"),(0,i.yg)("li",{parentName:"ul"},"Thanks to @shish, @cvergne and @mshapovalov for updating the docs!")),(0,i.yg)("h3",{id:"minor-changes"},"Minor Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#639 Added support for Symfony 7. @janatjak")),(0,i.yg)("h2",{id:"623"},"6.2.3"),(0,i.yg)("p",null,"Adds support for ",(0,i.yg)("inlineCode",{parentName:"p"},"Psr\\Container")," 1.1 with #601"),(0,i.yg)("h2",{id:"622"},"6.2.2"),(0,i.yg)("p",null,"This is a very simple release. We support Doctrine annotation 1.x and we've deprecated ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory::setDoctrineAnnotationReader")," in favor of native PHP attributes."),(0,i.yg)("h2",{id:"621"},"6.2.1"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Added support for new ",(0,i.yg)("inlineCode",{parentName:"li"},"Void")," return types, allowing use of ",(0,i.yg)("inlineCode",{parentName:"li"},"void")," from operation resolvers. #574"),(0,i.yg)("li",{parentName:"ul"},"Improvements with authorization middleware #571"),(0,i.yg)("li",{parentName:"ul"},"Updated vendor dependencies: #580 #558")),(0,i.yg)("h2",{id:"620"},"6.2.0"),(0,i.yg)("p",null,"Lots of little nuggets in this release! We're now targeting PHP ^8.1 and have testing on 8.2."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Better support for union types and enums: #530, #535, #561, #570"),(0,i.yg)("li",{parentName:"ul"},"Various bug and interface fixes: #532, #575, #564"),(0,i.yg)("li",{parentName:"ul"},"GraphQL v15 required: #542"),(0,i.yg)("li",{parentName:"ul"},"Lots of codebase improvements, more strict typing: #548")),(0,i.yg)("p",null,"A special thanks to @rusted-love and @oprypkhantc for their contributions."),(0,i.yg)("h2",{id:"610"},"6.1.0"),(0,i.yg)("p",null,"A shoutout to @bladl for his work on this release, improving the code for better typing and PHP 8.0 syntax updates!"),(0,i.yg)("h3",{id:"breaking-changes-1"},"Breaking Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#518 PSR-11 support now requires version 2"),(0,i.yg)("li",{parentName:"ul"},"#508 Due to some of the code improvements, additional typing has been added to some interfaces/classes. For instance, ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface::toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface::toGraphQLInputType")," now have the following signatures:")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"}," /**\n * @param (OutputType&GraphQLType)|null $subType\n *\n * @return OutputType&GraphQLType\n */\n public function toGraphQLOutputType(\n Type $type,\n OutputType|null $subType,\n ReflectionMethod|ReflectionProperty $reflector,\n DocBlock $docBlockObj\n ): OutputType;\n\n /**\n * @param (InputType&GraphQLType)|null $subType\n *\n * @return InputType&GraphQLType\n */\n public function toGraphQLInputType(\n Type $type,\n InputType|null $subType,\n string $argumentName,\n ReflectionMethod|ReflectionProperty $reflector,\n DocBlock $docBlockObj\n ): InputType;\n")),(0,i.yg)("h3",{id:"improvements-1"},"Improvements"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#510"),(0,i.yg)("li",{parentName:"ul"},"#508")),(0,i.yg)("h2",{id:"500"},"5.0.0"),(0,i.yg)("h3",{id:"dependencies"},"Dependencies"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Upgraded to using version 14.9 of ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/webonyx/graphql-php"},"webonyx/graphql-php"))),(0,i.yg)("h2",{id:"430"},"4.3.0"),(0,i.yg)("h3",{id:"breaking-change"},"Breaking change"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The method ",(0,i.yg)("inlineCode",{parentName:"li"},"setAnnotationCacheDir($directory)")," has been removed from the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),". The annotation\ncache will use your ",(0,i.yg)("inlineCode",{parentName:"li"},"Psr\\SimpleCache\\CacheInterface")," compliant cache handler set through the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),"\nconstructor.")),(0,i.yg)("h3",{id:"minor-changes-1"},"Minor changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Removed dependency for doctrine/cache and unified some of the cache layers following a PSR interface."),(0,i.yg)("li",{parentName:"ul"},"Cleaned up some of the documentation in an attempt to get things accurate with versioned releases.")),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h3",{id:"breaking-change-1"},"Breaking change"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h3",{id:"new-features-1"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/next/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Factory]"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Type]")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Field]")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/input-types#input-attribute"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Field]"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Logged]"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Right]"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Security]"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h3",{id:"breaking-change-2"},"Breaking change"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h3",{id:"new-features-2"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h3",{id:"minor-changes-2"},"Minor changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h3",{id:"miscellaneous"},"Miscellaneous"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h3",{id:"new-features-3"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"#[Type]")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Assertion]")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Security]")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"#[InjectUser]")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"#[Decorate]")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h3",{id:"symfony"},"Symfony"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h3",{id:"laravel"},"Laravel"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/next/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h3",{id:"internals"},"Internals"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2674],{81316:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>p,contentTitle:()=>r,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>s});var t=a(58168),i=(a(96540),a(15680));a(67443);const l={id:"changelog",title:"Changelog",sidebar_label:"Changelog"},r=void 0,o={unversionedId:"changelog",id:"changelog",title:"Changelog",description:"7.0.0",source:"@site/docs/CHANGELOG.md",sourceDirName:".",slug:"/changelog",permalink:"/docs/next/changelog",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/CHANGELOG.md",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"changelog",title:"Changelog",sidebar_label:"Changelog"},sidebar:"docs",previous:{title:"Semantic versioning",permalink:"/docs/next/semver"}},p={},s=[{value:"7.0.0",id:"700",level:2},{value:"Breaking Changes",id:"breaking-changes",level:3},{value:"New Features",id:"new-features",level:3},{value:"Improvements",id:"improvements",level:3},{value:"Minor Changes",id:"minor-changes",level:3},{value:"6.2.3",id:"623",level:2},{value:"6.2.2",id:"622",level:2},{value:"6.2.1",id:"621",level:2},{value:"6.2.0",id:"620",level:2},{value:"6.1.0",id:"610",level:2},{value:"Breaking Changes",id:"breaking-changes-1",level:3},{value:"Improvements",id:"improvements-1",level:3},{value:"5.0.0",id:"500",level:2},{value:"Dependencies",id:"dependencies",level:3},{value:"4.3.0",id:"430",level:2},{value:"Breaking change",id:"breaking-change",level:3},{value:"Minor changes",id:"minor-changes-1",level:3},{value:"4.2.0",id:"420",level:2},{value:"Breaking change",id:"breaking-change-1",level:3},{value:"New features",id:"new-features-1",level:3},{value:"4.1.0",id:"410",level:2},{value:"Breaking change",id:"breaking-change-2",level:3},{value:"New features",id:"new-features-2",level:3},{value:"Minor changes",id:"minor-changes-2",level:3},{value:"Miscellaneous",id:"miscellaneous",level:3},{value:"4.0.0",id:"400",level:2},{value:"New features",id:"new-features-3",level:3},{value:"Symfony",id:"symfony",level:3},{value:"Laravel",id:"laravel",level:3},{value:"Internals",id:"internals",level:3}],d={toc:s},u="wrapper";function g(e){let{components:n,...a}=e;return(0,i.yg)(u,(0,t.A)({},d,a,{components:n,mdxType:"MDXLayout"}),(0,i.yg)("h2",{id:"700"},"7.0.0"),(0,i.yg)("h3",{id:"breaking-changes"},"Breaking Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#664 Replaces ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/thecodingmachine/class-explorer"},"thecodingmachine/class-explorer")," with ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/alekitto/class-finder"},"kcs/class-finder")," resulting in the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory::setClassNameMapper")," being renamed to ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory::setFinder"),". This now expects an instance of ",(0,i.yg)("inlineCode",{parentName:"li"},"Kcs\\ClassFinder\\Finder")," instead of ",(0,i.yg)("inlineCode",{parentName:"li"},"Kcs\\ClassFinder\\Finder\\FinderInterface"),". @fogrye")),(0,i.yg)("h3",{id:"new-features"},"New Features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#649 Adds support for ",(0,i.yg)("inlineCode",{parentName:"li"},"subscription")," operations. @oojacoboo"),(0,i.yg)("li",{parentName:"ul"},"#612 Automatic query complexity analysis. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#611 Automatic persisted queries. @oprypkhantc")),(0,i.yg)("h3",{id:"improvements"},"Improvements"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#658 Improves on prefetching for nested fields. @grynchuk"),(0,i.yg)("li",{parentName:"ul"},"#646 Improves exception handling during schema parsing. @fogrye"),(0,i.yg)("li",{parentName:"ul"},"#636 Allows the use of middleware on construtor params/fields. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#623 Improves support for description arguments on types/fields. @downace"),(0,i.yg)("li",{parentName:"ul"},"#628 Properly handles ",(0,i.yg)("inlineCode",{parentName:"li"},"@param")," annotations for generics support on field annotated constructor arguments. @oojacoboo"),(0,i.yg)("li",{parentName:"ul"},"#584 Immutability improvements across the codebase. @oprypkhantc"),(0,i.yg)("li",{parentName:"ul"},"#588 Prefetch improvements. @oprpkhantc"),(0,i.yg)("li",{parentName:"ul"},"#606 Adds support for phpdoc descriptions and deprecation annotations on native enums. @mdoelker"),(0,i.yg)("li",{parentName:"ul"},"Thanks to @shish, @cvergne and @mshapovalov for updating the docs!")),(0,i.yg)("h3",{id:"minor-changes"},"Minor Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#639 Added support for Symfony 7. @janatjak")),(0,i.yg)("h2",{id:"623"},"6.2.3"),(0,i.yg)("p",null,"Adds support for ",(0,i.yg)("inlineCode",{parentName:"p"},"Psr\\Container")," 1.1 with #601"),(0,i.yg)("h2",{id:"622"},"6.2.2"),(0,i.yg)("p",null,"This is a very simple release. We support Doctrine annotation 1.x and we've deprecated ",(0,i.yg)("inlineCode",{parentName:"p"},"SchemaFactory::setDoctrineAnnotationReader")," in favor of native PHP attributes."),(0,i.yg)("h2",{id:"621"},"6.2.1"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Added support for new ",(0,i.yg)("inlineCode",{parentName:"li"},"Void")," return types, allowing use of ",(0,i.yg)("inlineCode",{parentName:"li"},"void")," from operation resolvers. #574"),(0,i.yg)("li",{parentName:"ul"},"Improvements with authorization middleware #571"),(0,i.yg)("li",{parentName:"ul"},"Updated vendor dependencies: #580 #558")),(0,i.yg)("h2",{id:"620"},"6.2.0"),(0,i.yg)("p",null,"Lots of little nuggets in this release! We're now targeting PHP ^8.1 and have testing on 8.2."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Better support for union types and enums: #530, #535, #561, #570"),(0,i.yg)("li",{parentName:"ul"},"Various bug and interface fixes: #532, #575, #564"),(0,i.yg)("li",{parentName:"ul"},"GraphQL v15 required: #542"),(0,i.yg)("li",{parentName:"ul"},"Lots of codebase improvements, more strict typing: #548")),(0,i.yg)("p",null,"A special thanks to @rusted-love and @oprypkhantc for their contributions."),(0,i.yg)("h2",{id:"610"},"6.1.0"),(0,i.yg)("p",null,"A shoutout to @bladl for his work on this release, improving the code for better typing and PHP 8.0 syntax updates!"),(0,i.yg)("h3",{id:"breaking-changes-1"},"Breaking Changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#518 PSR-11 support now requires version 2"),(0,i.yg)("li",{parentName:"ul"},"#508 Due to some of the code improvements, additional typing has been added to some interfaces/classes. For instance, ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface::toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface::toGraphQLInputType")," now have the following signatures:")),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"}," /**\n * @param (OutputType&GraphQLType)|null $subType\n *\n * @return OutputType&GraphQLType\n */\n public function toGraphQLOutputType(\n Type $type,\n OutputType|null $subType,\n ReflectionMethod|ReflectionProperty $reflector,\n DocBlock $docBlockObj\n ): OutputType;\n\n /**\n * @param (InputType&GraphQLType)|null $subType\n *\n * @return InputType&GraphQLType\n */\n public function toGraphQLInputType(\n Type $type,\n InputType|null $subType,\n string $argumentName,\n ReflectionMethod|ReflectionProperty $reflector,\n DocBlock $docBlockObj\n ): InputType;\n")),(0,i.yg)("h3",{id:"improvements-1"},"Improvements"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"#510"),(0,i.yg)("li",{parentName:"ul"},"#508")),(0,i.yg)("h2",{id:"500"},"5.0.0"),(0,i.yg)("h3",{id:"dependencies"},"Dependencies"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Upgraded to using version 14.9 of ",(0,i.yg)("a",{parentName:"li",href:"https://github.com/webonyx/graphql-php"},"webonyx/graphql-php"))),(0,i.yg)("h2",{id:"430"},"4.3.0"),(0,i.yg)("h3",{id:"breaking-change"},"Breaking change"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The method ",(0,i.yg)("inlineCode",{parentName:"li"},"setAnnotationCacheDir($directory)")," has been removed from the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),". The annotation\ncache will use your ",(0,i.yg)("inlineCode",{parentName:"li"},"Psr\\SimpleCache\\CacheInterface")," compliant cache handler set through the ",(0,i.yg)("inlineCode",{parentName:"li"},"SchemaFactory"),"\nconstructor.")),(0,i.yg)("h3",{id:"minor-changes-1"},"Minor changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Removed dependency for doctrine/cache and unified some of the cache layers following a PSR interface."),(0,i.yg)("li",{parentName:"ul"},"Cleaned up some of the documentation in an attempt to get things accurate with versioned releases.")),(0,i.yg)("h2",{id:"420"},"4.2.0"),(0,i.yg)("h3",{id:"breaking-change-1"},"Breaking change"),(0,i.yg)("p",null,"The method signature for ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," have been changed to the following:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n/**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\npublic function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n")),(0,i.yg)("h3",{id:"new-features-1"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/next/annotations-reference#input-annotation"},"@Input")," annotation is introduced as an alternative to ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Factory]"),". Now GraphQL input type can be created in the same manner as ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Type]")," in combination with ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Field]")," - ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/input-types#input-attribute"},"example"),"."),(0,i.yg)("li",{parentName:"ul"},"New attributes has been added to ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/annotations-reference#field-annotation"},"@Field")," annotation: ",(0,i.yg)("inlineCode",{parentName:"li"},"for"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"inputType")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"description"),"."),(0,i.yg)("li",{parentName:"ul"},"The following annotations now can be applied to class properties directly: ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Field]"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Logged]"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Right]"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@FailWith"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"@HideIfUnauthorized")," and ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Security]"),".")),(0,i.yg)("h2",{id:"410"},"4.1.0"),(0,i.yg)("h3",{id:"breaking-change-2"},"Breaking change"),(0,i.yg)("p",null,"There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility)."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("strong",{parentName:"li"},"ecodev/graphql-upload"),' package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.\nIf you are using GraphQL file uploads, you need to add ',(0,i.yg)("inlineCode",{parentName:"li"},"ecodev/graphql-upload")," to your ",(0,i.yg)("inlineCode",{parentName:"li"},"composer.json"),".")),(0,i.yg)("h3",{id:"new-features-2"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"All annotations can now be accessed as PHP 8 attributes"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"@deprecated")," annotation in your PHP code translates into deprecated fields in your GraphQL schema"),(0,i.yg)("li",{parentName:"ul"},"You can now specify the GraphQL name of the Enum types you define"),(0,i.yg)("li",{parentName:"ul"},"Added the possibility to inject pure Webonyx objects in GraphQLite schema")),(0,i.yg)("h3",{id:"minor-changes-2"},"Minor changes"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from ",(0,i.yg)("inlineCode",{parentName:"li"},"zend/diactoros")," to ",(0,i.yg)("inlineCode",{parentName:"li"},"laminas/diactoros")),(0,i.yg)("li",{parentName:"ul"},"Making the annotation cache directory configurable")),(0,i.yg)("h3",{id:"miscellaneous"},"Miscellaneous"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"Migrated from Travis to Github actions")),(0,i.yg)("h2",{id:"400"},"4.0.0"),(0,i.yg)("p",null,"This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely\nchanged."),(0,i.yg)("h3",{id:"new-features-3"},"New features"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"You can directly ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/inheritance-interfaces#mapping-interfaces"},"annotate a PHP interface with ",(0,i.yg)("inlineCode",{parentName:"a"},"#[Type]")," to make it a GraphQL interface")),(0,i.yg)("li",{parentName:"ul"},"You can autowire services in resolvers, thanks to the new ",(0,i.yg)("inlineCode",{parentName:"li"},"@Autowire")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/validation"},"user input validation")," (using the Symfony Validator or the Laravel validator or a custom ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Assertion]")," annotation"),(0,i.yg)("li",{parentName:"ul"},"Improved security handling:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)"),(0,i.yg)("li",{parentName:"ul"},"Added fine-grained security using the ",(0,i.yg)("inlineCode",{parentName:"li"},"#[Security]")," annotation. A field can now be ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/fine-grained-security"},"marked accessible or not depending on the context"),'.\nFor instance, you can restrict access to the field "viewsCount" of the type ',(0,i.yg)("inlineCode",{parentName:"li"},"BlogPost")," only for post that the current user wrote."),(0,i.yg)("li",{parentName:"ul"},"You can now inject the current logged user in any query / mutation / field using the ",(0,i.yg)("inlineCode",{parentName:"li"},"#[InjectUser]")," annotation"))),(0,i.yg)("li",{parentName:"ul"},"Performance:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can inject the ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/query-plan"},"Webonyx query plan in a parameter from a resolver")),(0,i.yg)("li",{parentName:"ul"},"You can use the ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/prefetch-method"},'dataloader pattern to improve performance drastically via the "prefetchMethod" attribute')))),(0,i.yg)("li",{parentName:"ul"},"Customizable error handling has been added:",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"You can throw ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/error-handling#many-errors-for-one-exception"},"many errors in one exception")," with ",(0,i.yg)("inlineCode",{parentName:"li"},"TheCodingMachine\\GraphQLite\\Exceptions\\GraphQLAggregateException")))),(0,i.yg)("li",{parentName:"ul"},"You can force input types using ",(0,i.yg)("inlineCode",{parentName:"li"},'@UseInputType(for="$id", inputType="ID!")')),(0,i.yg)("li",{parentName:"ul"},"You can extend an input types (just like you could extend an output type in v3) using ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/extend-input-type"},"the new ",(0,i.yg)("inlineCode",{parentName:"a"},"#[Decorate]")," annotation")),(0,i.yg)("li",{parentName:"ul"},"In a factory, you can ",(0,i.yg)("a",{parentName:"li",href:"input-types#ignoring-some-parameters"},"exclude some optional parameters from the GraphQL schema"))),(0,i.yg)("p",null,"Many extension points have been added"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)'),(0,i.yg)("li",{parentName:"ul"},"Added ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/field-middlewares"},'"field middlewares"')," (useful to add middleware that modify the way GraphQL fields are handled)"),(0,i.yg)("li",{parentName:"ul"},"Added a ",(0,i.yg)("a",{parentName:"li",href:"/docs/next/argument-resolving"},'"parameter type mapper"')," (useful to add customize parameter resolution or add custom annotations related to parameters)")),(0,i.yg)("p",null,"New framework specific features:"),(0,i.yg)("h3",{id:"symfony"},"Symfony"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},'The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)')),(0,i.yg)("h3",{id:"laravel"},"Laravel"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/next/laravel-package-advanced#support-for-pagination"},"Native integration with the Laravel paginator")," has been added")),(0,i.yg)("h3",{id:"internals"},"Internals"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder")," class has been split in many different services (",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilder"),", ",(0,i.yg)("inlineCode",{parentName:"li"},"TypeHandler"),", and a\nchain of ",(0,i.yg)("em",{parentName:"li"},"root type mappers"),")"),(0,i.yg)("li",{parentName:"ul"},"The ",(0,i.yg)("inlineCode",{parentName:"li"},"FieldsBuilderFactory")," class has been completely removed."),(0,i.yg)("li",{parentName:"ul"},"Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points\nthan 3.x. Try it out!")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/caa79efa.e50dc4d6.js b/assets/js/caa79efa.48c35a3e.js similarity index 99% rename from assets/js/caa79efa.e50dc4d6.js rename to assets/js/caa79efa.48c35a3e.js index f74e4f7b6f..bdd07a776b 100644 --- a/assets/js/caa79efa.e50dc4d6.js +++ b/assets/js/caa79efa.48c35a3e.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9062],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[s,u]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=s??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),h(e)}),[u,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=u[a].value;n!==l&&(y(t),s(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},29450:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},s=void 0,u={unversionedId:"custom-types",id:"version-4.3/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-4.3/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/4.3/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/custom-types.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"version-4.3/docs",previous:{title:"Pagination",permalink:"/docs/4.3/pagination"},next:{title:"Custom annotations",permalink:"/docs/4.3/field-middlewares"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9062],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const p={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(p.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>v});var n=a(58168),r=a(96540),p=a(20053),o=a(23104),l=a(56347),s=a(57485),u=a(31682),i=a(89466);function y(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??y(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function d(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:a}=e;const n=(0,l.W6)(),p=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,s.aZ)(p),(0,r.useCallback)((e=>{if(!p)return;const t=new URLSearchParams(n.location.search);t.set(p,e),n.replace({...n.location,search:t.toString()})}),[p,n])]}function h(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,p=c(e),[o,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:p}))),[s,u]=m({queryString:a,groupId:n}),[y,h]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,p]=(0,i.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&p.set(e)}),[a,p])]}({groupId:n}),g=(()=>{const e=s??y;return d({value:e,tabValues:p})?e:null})();(0,r.useLayoutEffect)((()=>{g&&l(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:p}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),h(e)}),[u,h,p]),tabValues:p}}var g=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:l,selectValue:s,tabValues:u}=e;const i=[],{blockElementScrollPositionUntilNextRender:y}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=i.indexOf(t),n=u[a].value;n!==l&&(y(t),s(n))},d=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=i.indexOf(e.currentTarget)+1;t=i[a]??i[0];break}case"ArrowLeft":{const a=i.indexOf(e.currentTarget)-1;t=i[a]??i[i.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,p.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>i.push(e),onKeyDown:d,onClick:c},o,{className:(0,p.A)("tabs__item",f.tabItem,o?.className,{"tabs__item--active":l===t})}),a??t)})))}function T(e){let{lazy:t,children:a,selectedValue:n}=e;const p=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=p.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},p.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function N(e){const t=h(e);return r.createElement("div",{className:(0,p.A)("tabs-container",f.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(T,(0,n.A)({},e,t)))}function v(e){const t=(0,g.A)();return r.createElement(N,(0,n.A)({key:String(t)},e))}},29450:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>y});var n=a(58168),r=(a(96540),a(15680)),p=(a(67443),a(11470)),o=a(19365);const l={id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},s=void 0,u={unversionedId:"custom-types",id:"version-4.3/custom-types",title:"Custom types",description:"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.",source:"@site/versioned_docs/version-4.3/custom-types.mdx",sourceDirName:".",slug:"/custom-types",permalink:"/docs/4.3/custom-types",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/custom-types.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"custom-types",title:"Custom types",sidebar_label:"Custom types"},sidebar:"version-4.3/docs",previous:{title:"Pagination",permalink:"/docs/4.3/pagination"},next:{title:"Custom annotations",permalink:"/docs/4.3/field-middlewares"}},i={},y=[{value:"Usage",id:"usage",level:2},{value:"Registering a custom output type (advanced)",id:"registering-a-custom-output-type-advanced",level:2},{value:"Symfony users",id:"symfony-users",level:3},{value:"Other frameworks",id:"other-frameworks",level:3},{value:"Registering a custom scalar type (advanced)",id:"registering-a-custom-scalar-type-advanced",level:2}],c={toc:y},d="wrapper";function m(e){let{components:t,...a}=e;return(0,r.yg)(d,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type(class: Product::class)]\nclass ProductType\n{\n #[Field]\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type(class=Product::class)\n */\nclass ProductType\n{\n /**\n * @Field\n */\n public function getId(Product $source): string\n {\n return $source->getId();\n }\n}\n")))),(0,r.yg)("p",null,"In the example above, GraphQLite will generate a GraphQL schema with a field ",(0,r.yg)("inlineCode",{parentName:"p"},"id")," of type ",(0,r.yg)("inlineCode",{parentName:"p"},"string"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"type Product {\n id: String!\n}\n")),(0,r.yg)("p",null,"GraphQL comes with an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," scalar type. But PHP has no such type. So GraphQLite does not know when a variable\nis an ",(0,r.yg)("inlineCode",{parentName:"p"},"ID")," or not."),(0,r.yg)("p",null,"You can help GraphQLite by manually specifying the output type to use:"),(0,r.yg)(p.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' #[Field(outputType: "ID")]\n'))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},' /**\n * @Field(name="id", outputType="ID")\n */\n')))),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute will map the return value of the method to the output type passed in parameter."),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"outputType")," attribute in the following annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Query")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Mutation")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@Field")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField"))),(0,r.yg)("h2",{id:"registering-a-custom-output-type-advanced"},"Registering a custom output type (advanced)"),(0,r.yg)("p",null,"In order to create a custom output type, you need to:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},"Design a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ObjectType"),"."),(0,r.yg)("li",{parentName:"ol"},"Register this class in the GraphQL schema.")),(0,r.yg)("p",null,"You'll find more details on the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/type-system/object-types/"},"Webonyx documentation"),"."),(0,r.yg)("hr",null),(0,r.yg)("p",null,"In order to find existing types, the schema is using ",(0,r.yg)("em",{parentName:"p"},"type mappers")," (classes implementing the ",(0,r.yg)("inlineCode",{parentName:"p"},"TypeMapperInterface")," interface)."),(0,r.yg)("p",null,"You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework\nyou use."),(0,r.yg)("h3",{id:"symfony-users"},"Symfony users"),(0,r.yg)("p",null,"Any class extending ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Type\\Definition\\ObjectType")," (and available in the container) will be automatically detected\nby Symfony and added to the schema."),(0,r.yg)("p",null,"If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type\nas a service and use the ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql.output_type")," tag:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-yaml"},"# config/services.yaml\nservices:\n App\\MyOutputType:\n tags:\n - { name: 'graphql.output_type', class: 'App\\MyPhpClass' }\n")),(0,r.yg)("h3",{id:"other-frameworks"},"Other frameworks"),(0,r.yg)("p",null,"The easiest way is to use a ",(0,r.yg)("inlineCode",{parentName:"p"},"StaticTypeMapper"),". Use this class to register custom output types."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"// Sample code:\n$staticTypeMapper = new StaticTypeMapper();\n\n// Let's register a type that maps by default to the \"MyClass\" PHP class\n$staticTypeMapper->setTypes([\n MyClass::class => new MyCustomOutputType()\n]);\n\n// If you don't want your output type to map to any PHP class by default, use:\n$staticTypeMapper->setNotMappedTypes([\n new MyCustomOutputType()\n]);\n\n// Register the static type mapper in your application using the SchemaFactory instance\n$schemaFactory->addTypeMapper($staticTypeMapper);\n")),(0,r.yg)("h2",{id:"registering-a-custom-scalar-type-advanced"},"Registering a custom scalar type (advanced)"),(0,r.yg)("p",null,"If you need to add custom scalar types, first, check the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite-misc-types"},"GraphQLite Misc. Types library"),'.\nIt contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.'),(0,r.yg)("p",null,"You still need to develop your custom scalar type? Ok, let's get started."),(0,r.yg)("p",null,"In order to add a scalar type in GraphQLite, you need to:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"create a ",(0,r.yg)("a",{parentName:"li",href:"https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types"},"Webonyx custom scalar type"),".\nYou do this by creating a class that extends ",(0,r.yg)("inlineCode",{parentName:"li"},"GraphQL\\Type\\Definition\\ScalarType"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the ',(0,r.yg)("inlineCode",{parentName:"li"},"RootTypeMapperInterface"),"."),(0,r.yg)("li",{parentName:"ul"},'create a "type mapper factory" that will be in charge of creating your "type mapper".')),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"interface RootTypeMapperInterface\n{\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;\n\n /**\n * @param \\ReflectionMethod|\\ReflectionProperty $reflector\n */\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;\n\n public function mapNameToType(string $typeName): NamedType;\n}\n")),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLOutputType")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"toGraphQLInputType")," are meant to map a return type (for output types) or a parameter type (for input types)\nto your GraphQL scalar type. Return your scalar type if there is a match or ",(0,r.yg)("inlineCode",{parentName:"p"},"null")," if there no match."),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"mapNameToType")," should return your GraphQL scalar type if ",(0,r.yg)("inlineCode",{parentName:"p"},"$typeName")," is the name of your scalar type."),(0,r.yg)("p",null,"RootTypeMapper are organized ",(0,r.yg)("strong",{parentName:"p"},"in a chain")," (they are actually middlewares).\nEach instance of a ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapper")," holds a reference on the next root type mapper to be called in the chain."),(0,r.yg)("p",null,"For instance:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'class AnyScalarTypeMapper implements RootTypeMapperInterface\n{\n /** @var RootTypeMapperInterface */\n private $next;\n\n public function __construct(RootTypeMapperInterface $next)\n {\n $this->next = $next;\n }\n\n public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);\n }\n\n public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType\n {\n if ($type instanceof Scalar) {\n // AnyScalarType is a class implementing the Webonyx ScalarType type.\n return AnyScalarType::getInstance();\n }\n // If the PHPDoc type is not "Scalar", let\'s pass the control to the next type mapper in the chain\n return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);\n }\n\n /**\n * Returns a GraphQL type by name.\n * If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should\n * also map these types by name in the "mapNameToType" method.\n *\n * @param string $typeName The name of the GraphQL type\n * @return NamedType|null\n */\n public function mapNameToType(string $typeName): ?NamedType\n {\n if ($typeName === AnyScalarType::NAME) {\n return AnyScalarType::getInstance();\n }\n return null;\n }\n}\n')),(0,r.yg)("p",null,"Now, in order to create an instance of your ",(0,r.yg)("inlineCode",{parentName:"p"},"AnyScalarTypeMapper")," class, you need an instance of the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper in the chain.\nHow do you get the ",(0,r.yg)("inlineCode",{parentName:"p"},"$next")," type mapper? Through a factory:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface\n{\n public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface\n {\n return new AnyScalarTypeMapper($next);\n }\n}\n")),(0,r.yg)("p",null,"Now, you need to register this factory in your application, and we are done."),(0,r.yg)("p",null,"You can register your own root mapper factories using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addRootTypeMapperFactory()")," method."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());\n")),(0,r.yg)("p",null,'If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service\nis automatically tagged with the "graphql.root_type_mapper_factory" tag).'))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/cb01db44.4b8a5915.js b/assets/js/cb01db44.9d4869e3.js similarity index 99% rename from assets/js/cb01db44.4b8a5915.js rename to assets/js/cb01db44.9d4869e3.js index d03a4a3ba0..2bea5e35e5 100644 --- a/assets/js/cb01db44.4b8a5915.js +++ b/assets/js/cb01db44.9d4869e3.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8703],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var t=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),l=a(20053),i=a(23104),s=a(56347),p=a(57485),c=a(31682),o=a(89466);function u(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function m(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function d(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[p,c]=g({queryString:a,groupId:t}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=p??u;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),c(e),y(e)}),[c,y,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:p,tabValues:c}=e;const o=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=c[a].value;t!==s&&(u(n),p(t))},d=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:d,onClick:m},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function N(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(v,(0,t.A)({},e,n)))}function T(e){const n=(0,h.A)();return r.createElement(N,(0,t.A)({key:String(n)},e))}},49784:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>p,default:()=>g,frontMatter:()=>s,metadata:()=>c,toc:()=>u});var t=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const s={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},p=void 0,c={unversionedId:"inheritance-interfaces",id:"version-5.0/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-5.0/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/5.0/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/inheritance-interfaces.mdx",tags:[],version:"5.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"version-5.0/docs",previous:{title:"Input types",permalink:"/docs/5.0/input-types"},next:{title:"Error handling",permalink:"/docs/5.0/error-handling"}},o={},u=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],m={toc:u},d="wrapper";function g(e){let{components:n,...a}=e;return(0,r.yg)(d,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,r.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,r.yg)("p",null,"Let's say you have two classes, ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,r.yg)("p",null,"Written in ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,r.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,r.yg)("p",null,"The GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,r.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,r.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,r.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,r.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will translate in GraphQL schema as:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,r.yg)("p",null,"Please note that you do not need to put the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,r.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,r.yg)("p",null,"You don't have to explicitly put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")))),(0,r.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,r.yg)("p",null,"In the example above, because the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,r.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8703],{19365:(e,n,a)=>{a.d(n,{A:()=>i});var t=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:n,hidden:a,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:a},n)}},11470:(e,n,a)=>{a.d(n,{A:()=>T});var t=a(58168),r=a(96540),l=a(20053),i=a(23104),s=a(56347),p=a(57485),c=a(31682),o=a(89466);function u(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:a,attributes:t,default:r}}=e;return{value:n,label:a,attributes:t,default:r}}))}function m(e){const{values:n,children:a}=e;return(0,r.useMemo)((()=>{const e=n??u(a);return function(e){const n=(0,c.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,a])}function d(e){let{value:n,tabValues:a}=e;return a.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:a}=e;const t=(0,s.W6)(),l=function(e){let{queryString:n=!1,groupId:a}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:n,groupId:a});return[(0,p.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const n=new URLSearchParams(t.location.search);n.set(l,e),t.replace({...t.location,search:n.toString()})}),[l,t])]}function y(e){const{defaultValue:n,queryString:a=!1,groupId:t}=e,l=m(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:n,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const t=a.find((e=>e.default))??a[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:l}))),[p,c]=g({queryString:a,groupId:t}),[u,y]=function(e){let{groupId:n}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(n),[t,l]=(0,o.Dv)(a);return[t,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:t}),h=(()=>{const e=p??u;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);s(e),c(e),y(e)}),[c,y,l]),tabValues:l}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:a,selectedValue:s,selectValue:p,tabValues:c}=e;const o=[],{blockElementScrollPositionUntilNextRender:u}=(0,i.a_)(),m=e=>{const n=e.currentTarget,a=o.indexOf(n),t=c[a].value;t!==s&&(u(n),p(t))},d=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const a=o.indexOf(e.currentTarget)+1;n=o[a]??o[0];break}case"ArrowLeft":{const a=o.indexOf(e.currentTarget)-1;n=o[a]??o[o.length-1];break}}n?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},n)},c.map((e=>{let{value:n,label:a,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:s===n?0:-1,"aria-selected":s===n,key:n,ref:e=>o.push(e),onKeyDown:d,onClick:m},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===n})}),a??n)})))}function v(e){let{lazy:n,children:a,selectedValue:t}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(n){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,n)=>(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==t}))))}function N(e){const n=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(b,(0,t.A)({},e,n)),r.createElement(v,(0,t.A)({},e,n)))}function T(e){const n=(0,h.A)();return r.createElement(N,(0,t.A)({key:String(n)},e))}},49784:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>o,contentTitle:()=>p,default:()=>g,frontMatter:()=>s,metadata:()=>c,toc:()=>u});var t=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),i=a(19365);const s={id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},p=void 0,c={unversionedId:"inheritance-interfaces",id:"version-5.0/inheritance-interfaces",title:"Inheritance and interfaces",description:"Modeling inheritance",source:"@site/versioned_docs/version-5.0/inheritance-interfaces.mdx",sourceDirName:".",slug:"/inheritance-interfaces",permalink:"/docs/5.0/inheritance-interfaces",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-5.0/inheritance-interfaces.mdx",tags:[],version:"5.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"inheritance-interfaces",title:"Inheritance and interfaces",sidebar_label:"Inheritance and interfaces"},sidebar:"version-5.0/docs",previous:{title:"Input types",permalink:"/docs/5.0/input-types"},next:{title:"Error handling",permalink:"/docs/5.0/error-handling"}},o={},u=[{value:"Modeling inheritance",id:"modeling-inheritance",level:2},{value:"Mapping interfaces",id:"mapping-interfaces",level:2},{value:"Implementing interfaces",id:"implementing-interfaces",level:3},{value:"Interfaces without an explicit implementing type",id:"interfaces-without-an-explicit-implementing-type",level:3}],m={toc:u},d="wrapper";function g(e){let{components:n,...a}=e;return(0,r.yg)(d,(0,t.A)({},m,a,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"modeling-inheritance"},"Modeling inheritance"),(0,r.yg)("p",null,"Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces."),(0,r.yg)("p",null,"Let's say you have two classes, ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," (which extends ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact"),"):"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass Contact\n{\n // ...\n}\n\n#[Type]\nclass User extends Contact\n{\n // ...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass Contact\n{\n // ...\n}\n\n/**\n * @Type\n */\nclass User extends Contact\n{\n // ...\n}\n")))),(0,r.yg)("p",null,"Now, let's assume you have a query that returns a contact:"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n #[Query]\n public function getContact(): Contact\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class ContactController\n{\n /**\n * @Query()\n */\n public function getContact(): Contact\n {\n // ...\n }\n}\n")))),(0,r.yg)("p",null,"When writing your GraphQL query, you are able to use fragments to retrieve fields from the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"contact {\n name\n ... User {\n email\n }\n}\n")),(0,r.yg)("p",null,"Written in ",(0,r.yg)("a",{parentName:"p",href:"https://graphql.org/learn/schema/#type-language"},"GraphQL type language"),", the representation of types\nwould look like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype Contact implements ContactInterface {\n // List of fields declared in Contact class\n}\n\ntype User implements ContactInterface {\n // List of fields declared in Contact and User classes\n}\n")),(0,r.yg)("p",null,"Behind the scene, GraphQLite will detect that the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," class is extended by the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class.\nBecause the class is extended, a GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface")," interface is created dynamically."),(0,r.yg)("p",null,"The GraphQL ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," type will also automatically implement this ",(0,r.yg)("inlineCode",{parentName:"p"},"ContactInterface"),". The interface contains all the fields\navailable in the ",(0,r.yg)("inlineCode",{parentName:"p"},"Contact")," type."),(0,r.yg)("h2",{id:"mapping-interfaces"},"Mapping interfaces"),(0,r.yg)("p",null,"If you want to create a pure GraphQL interface, you can also add a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on a PHP interface."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\ninterface UserInterface\n{\n #[Field]\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\ninterface UserInterface\n{\n /**\n * @Field\n */\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will automatically create a GraphQL interface whose description is:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n")),(0,r.yg)("h3",{id:"implementing-interfaces"},"Implementing interfaces"),(0,r.yg)("p",null,'You don\'t have to do anything special to implement an interface in your GraphQL types.\nSimply "implement" the interface in PHP and you are done!'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")))),(0,r.yg)("p",null,"This will translate in GraphQL schema as:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype User implements UserInterface {\n userName: String!\n}\n")),(0,r.yg)("p",null,"Please note that you do not need to put the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation again in the implementing class."),(0,r.yg)("h3",{id:"interfaces-without-an-explicit-implementing-type"},"Interfaces without an explicit implementing type"),(0,r.yg)("p",null,"You don't have to explicitly put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotation on the class implementing the interface (though this\nis usually a good idea)."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no #Type attribute\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n #[Query]\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * Look, this class has no @Type annotation\n */\nclass User implements UserInterface\n{\n public function getUserName(): string;\n}\n")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class UserController\n{\n /**\n * @Query()\n */\n public function getUser(): UserInterface // This will work!\n {\n // ...\n }\n}\n")))),(0,r.yg)("div",{class:"alert alert--info"},'If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it will create an object type "on the fly".'),(0,r.yg)("p",null,"In the example above, because the ",(0,r.yg)("inlineCode",{parentName:"p"},"User")," class has no ",(0,r.yg)("inlineCode",{parentName:"p"},"@Type")," annotations, GraphQLite will\ncreate a ",(0,r.yg)("inlineCode",{parentName:"p"},"UserImpl")," type that implements ",(0,r.yg)("inlineCode",{parentName:"p"},"UserInterface"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"interface UserInterface {\n userName: String!\n}\n\ntype UserImpl implements UserInterface {\n userName: String!\n}\n")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/cc08685c.a1c356b7.js b/assets/js/cc08685c.65277fd4.js similarity index 98% rename from assets/js/cc08685c.a1c356b7.js rename to assets/js/cc08685c.65277fd4.js index 4d47aa71cf..86017d6a4c 100644 --- a/assets/js/cc08685c.a1c356b7.js +++ b/assets/js/cc08685c.65277fd4.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5515],{604:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,o={unversionedId:"internals",id:"version-4.2/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-4.2/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/4.2/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/internals.md",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"version-4.2/docs",previous:{title:"Laravel specific features",permalink:"/docs/4.2/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/4.2/troubleshooting"}},l={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.2/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5515],{604:(e,a,p)=>{p.r(a),p.d(a,{assets:()=>l,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>y});var t=p(58168),r=(p(96540),p(15680)),n=p(67443);const i={id:"internals",title:"Internals",sidebar_label:"Internals"},s=void 0,o={unversionedId:"internals",id:"version-4.2/internals",title:"Internals",description:"Mapping types",source:"@site/versioned_docs/version-4.2/internals.md",sourceDirName:".",slug:"/internals",permalink:"/docs/4.2/internals",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/internals.md",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"internals",title:"Internals",sidebar_label:"Internals"},sidebar:"version-4.2/docs",previous:{title:"Laravel specific features",permalink:"/docs/4.2/laravel-package-advanced"},next:{title:"Troubleshooting",permalink:"/docs/4.2/troubleshooting"}},l={},y=[{value:"Mapping types",id:"mapping-types",level:2},{value:"Root type mappers",id:"root-type-mappers",level:2},{value:"Class type mappers",id:"class-type-mappers",level:2},{value:"Registering a type mapper in Symfony",id:"registering-a-type-mapper-in-symfony",level:3},{value:"Registering a type mapper using the SchemaFactory",id:"registering-a-type-mapper-using-the-schemafactory",level:3},{value:"Recursive type mappers",id:"recursive-type-mappers",level:2},{value:"Parameter mapper middlewares",id:"parameter-mapper-middlewares",level:2}],m={toc:y},g="wrapper";function u(e){let{components:a,...p}=e;return(0,r.yg)(g,(0,t.A)({},m,p,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"mapping-types"},"Mapping types"),(0,r.yg)("p",null,'The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of\n"type mappers".'),(0,r.yg)("p",null,"GraphQLite contains 4 categories of type mappers:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Parameter mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Root type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Recursive (class) type mappers")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"(class) type mappers"))),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n subgraph RecursiveTypeMapperInterface\n BaseTypeMapper--\x3eRecursiveTypeMapper\n end\n subgraph TypeMapperInterface\n RecursiveTypeMapper--\x3eYourCustomTypeMapper\n YourCustomTypeMapper--\x3ePorpaginasTypeMapper\n PorpaginasTypeMapper--\x3eGlobTypeMapper\n end\n class YourCustomRootTypeMapper,YourCustomTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"root-type-mappers"},"Root type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RootTypeMapperInterface")),")"),(0,r.yg)("p",null,"These type mappers are the first type mappers called."),(0,r.yg)("p",null,"They are responsible for:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},'mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)'),(0,r.yg)("li",{parentName:"ul"},'detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")'),(0,r.yg)("li",{parentName:"ul"},"mapping list types (mapping a PHP array to a GraphQL list)"),(0,r.yg)("li",{parentName:"ul"},"mapping union types"),(0,r.yg)("li",{parentName:"ul"},"mapping enums")),(0,r.yg)("p",null,"Root type mappers have access to the ",(0,r.yg)("em",{parentName:"p"},"context"),' of a type: they can access the PHP DocBlock and read annotations.\nIf you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".'),(0,r.yg)("p",null,"GraphQLite provides 6 classes implementing ",(0,r.yg)("inlineCode",{parentName:"p"},"RootTypeMapperInterface"),":"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"NullableTypeMapperAdapter"),": a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompoundTypeMapper"),": a type mapper in charge of union types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"IteratorTypeMapper"),": a type mapper in charge of iterable types (for instance: ",(0,r.yg)("inlineCode",{parentName:"li"},"MyIterator|User[]"),")"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"MyCLabsEnumTypeMapper"),": maps MyCLabs/enum types to GraphQL enum types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"BaseTypeMapper"),': maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.'),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"FinalRootTypeMapper"),": the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.")),(0,r.yg)("p",null,"Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper."),(0,r.yg)(n.K,{chart:"graph TD;\n classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;\n subgraph RootTypeMapperInterface\n NullableTypeMapperAdapter--\x3eCompoundTypeMapper\n CompoundTypeMapper--\x3eIteratorTypeMapper\n IteratorTypeMapper--\x3eYourCustomRootTypeMapper\n YourCustomRootTypeMapper--\x3eMyCLabsEnumTypeMapper\n MyCLabsEnumTypeMapper--\x3eBaseTypeMapper\n BaseTypeMapper--\x3eFinalRootTypeMapper\n end\n class YourCustomRootTypeMapper custom;",mdxType:"Mermaid"}),(0,r.yg)("h2",{id:"class-type-mappers"},"Class type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"TypeMapperInterface")),")"),(0,r.yg)("p",null,"Class type mappers are mapping PHP classes to GraphQL object types."),(0,r.yg)("p",null,"GraphQLite provide 3 default implementations:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"CompositeTypeMapper"),": a type mapper that delegates mapping to other type mappers using the Composite Design Pattern."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"GlobTypeMapper"),": scans classes in a directory for the ",(0,r.yg)("inlineCode",{parentName:"li"},"@Type")," or ",(0,r.yg)("inlineCode",{parentName:"li"},"@ExtendType")," annotation and maps those to GraphQL types"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"PorpaginasTypeMapper"),": maps and class implementing the Porpaginas ",(0,r.yg)("inlineCode",{parentName:"li"},"Result")," interface to a ",(0,r.yg)("a",{parentName:"li",href:"/docs/4.2/pagination"},"special paginated type"),".")),(0,r.yg)("h3",{id:"registering-a-type-mapper-in-symfony"},"Registering a type mapper in Symfony"),(0,r.yg)("p",null,'If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.'),(0,r.yg)("h3",{id:"registering-a-type-mapper-using-the-schemafactory"},"Registering a type mapper using the SchemaFactory"),(0,r.yg)("p",null,"If you are using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory")," to bootstrap GraphQLite, you can register a type mapper using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addTypeMapper")," method."),(0,r.yg)("h2",{id:"recursive-type-mappers"},"Recursive type mappers"),(0,r.yg)("p",null,"(Classes implementing the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"RecursiveTypeMapperInterface")),")"),(0,r.yg)("p",null,"There is only one implementation of the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapperInterface"),": the ",(0,r.yg)("inlineCode",{parentName:"p"},"RecursiveTypeMapper"),"."),(0,r.yg)("p",null,'Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.\nThis is the role of the "recursive type mapper".'),(0,r.yg)("p",null,'Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".'),(0,r.yg)("p",null,'Since "B" ',(0,r.yg)("em",{parentName:"p"},"is a"),' "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".'),(0,r.yg)("h2",{id:"parameter-mapper-middlewares"},"Parameter mapper middlewares"),(0,r.yg)("p",null,'"Parameter middlewares" are used to decide what argument should be injected into a parameter.'),(0,r.yg)("p",null,"Let's have a look at a simple query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Query\n * @return Product[]\n */\npublic function products(ResolveInfo $info): array\n")),(0,r.yg)("p",null,"As you may know, ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/query-plan"},"the ",(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfo")," object injected in this query comes from Webonyx/GraphQL-PHP library"),".\nGraphQLite knows that is must inject a ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," instance because it comes with a ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ResolveInfoParameterHandler"))," class\nthat implements the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php"},(0,r.yg)("inlineCode",{parentName:"a"},"ParameterMiddlewareInterface")),")."),(0,r.yg)("p",null,"You can register your own parameter middlewares using the ",(0,r.yg)("inlineCode",{parentName:"p"},"SchemaFactory::addParameterMiddleware()"),' method, or by tagging the\nservice as "graphql.parameter_middleware" if you are using the Symfony bundle.'),(0,r.yg)("div",{class:"alert alert--info"},"Use a parameter middleware if you want to inject an argument in a method and if this argument is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)"))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/cc1c02fe.165c033e.js b/assets/js/cc1c02fe.61b2623b.js similarity index 93% rename from assets/js/cc1c02fe.165c033e.js rename to assets/js/cc1c02fe.61b2623b.js index bd2a8032b7..f510c7d5f6 100644 --- a/assets/js/cc1c02fe.165c033e.js +++ b/assets/js/cc1c02fe.61b2623b.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7492],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),o=n(20053),i=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function g(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function y(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=d(e),[i,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[s,u]=m({queryString:n,groupId:a}),[p,y]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),h=(()=>{const e=s??p;return g({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{h&&l(h)}),[h]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var h=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),s(a))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},i,{className:(0,o.A)("tabs__item",b.tabItem,i?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function P(e){const t=y(e);return r.createElement("div",{className:(0,o.A)("tabs-container",b.tabList)},r.createElement(f,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,h.A)();return r.createElement(P,(0,a.A)({key:String(t)},e))}},27796:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),o=(n(67443),n(11470)),i=n(19365);const l={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},s=void 0,u={unversionedId:"doctrine-annotations-attributes",id:"version-4.3/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-4.3/doctrine-annotations-attributes.mdx",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/4.3/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/doctrine-annotations-attributes.mdx",tags:[],version:"4.3",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},sidebar:"version-4.3/docs",previous:{title:"Migrating",permalink:"/docs/4.3/migrating"},next:{title:"Annotations reference",permalink:"/docs/4.3/annotations-reference"}},c={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(g,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,r.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in a future release."),(0,r.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support. This was the purpose of the ',(0,r.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,r.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"Please note that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,r.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,r.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,r.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("strong",null,"Heads up!"),(0,r.yg)("p",null,"Some IDEs provide support for Doctrine annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",null,"PhpStorm via the ",(0,r.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,r.yg)("li",null,"Eclipse via the ",(0,r.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,r.yg)("li",null,"Netbeans has native support")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"We strongly recommend using an IDE that has Doctrine annotations support.\n"))),(0,r.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,r.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,r.yg)("p",null,"The same code can be written this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,r.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,r.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,r.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,r.yg)("p",null,"They support the same attributes too."),(0,r.yg)("p",null,"A few notable differences:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,r.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,r.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,r.yg)("p",null,"Let's take an example with the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/autowiring"},(0,r.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[7492],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),o=n(20053),i=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function g(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function h(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=d(e),[i,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[s,u]=m({queryString:n,groupId:a}),[p,h]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),y=(()=>{const e=s??p;return g({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),h(e)}),[u,h,o]),tabValues:o}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),s(a))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},i,{className:(0,o.A)("tabs__item",b.tabItem,i?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function P(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",b.tabList)},r.createElement(f,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,y.A)();return r.createElement(P,(0,a.A)({key:String(t)},e))}},27796:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),o=(n(67443),n(11470)),i=n(19365);const l={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},s=void 0,u={unversionedId:"doctrine-annotations-attributes",id:"version-4.3/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-4.3/doctrine-annotations-attributes.mdx",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/4.3/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.3/doctrine-annotations-attributes.mdx",tags:[],version:"4.3",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},sidebar:"version-4.3/docs",previous:{title:"Migrating",permalink:"/docs/4.3/migrating"},next:{title:"Annotations reference",permalink:"/docs/4.3/annotations-reference"}},c={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(g,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,r.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in a future release."),(0,r.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support. This was the purpose of the ',(0,r.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,r.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"Please note that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,r.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,r.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,r.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("strong",null,"Heads up!"),(0,r.yg)("p",null,"Some IDEs provide support for Doctrine annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",null,"PhpStorm via the ",(0,r.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,r.yg)("li",null,"Eclipse via the ",(0,r.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,r.yg)("li",null,"Netbeans has native support")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"We strongly recommend using an IDE that has Doctrine annotations support.\n"))),(0,r.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,r.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,r.yg)("p",null,"The same code can be written this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,r.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,r.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,r.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,r.yg)("p",null,"They support the same attributes too."),(0,r.yg)("p",null,"A few notable differences:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,r.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,r.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,r.yg)("p",null,"Let's take an example with the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.3/autowiring"},(0,r.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/cc1f18af.1fc35cb3.js b/assets/js/cc1f18af.2f6cfe23.js similarity index 98% rename from assets/js/cc1f18af.1fc35cb3.js rename to assets/js/cc1f18af.2f6cfe23.js index 099d6a3bac..da408df61c 100644 --- a/assets/js/cc1f18af.1fc35cb3.js +++ b/assets/js/cc1f18af.2f6cfe23.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3177],{56970:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"version-6.0/laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.0/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/6.0/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/laravel-package.md",tags:[],version:"6.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"docs",previous:{title:"Symfony bundle",permalink:"/docs/6.0/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/6.0/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},'$ php artisan vendor:publish --provider="TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider"\n')),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3177],{56970:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var t=n(58168),r=(n(96540),n(15680));n(67443);const l={id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},o=void 0,i={unversionedId:"laravel-package",id:"version-6.0/laravel-package",title:"Getting started with Laravel",description:"Be advised! This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the Github repository.",source:"@site/versioned_docs/version-6.0/laravel-package.md",sourceDirName:".",slug:"/laravel-package",permalink:"/docs/6.0/laravel-package",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-6.0/laravel-package.md",tags:[],version:"6.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package",title:"Getting started with Laravel",sidebar_label:"Laravel package"},sidebar:"docs",previous:{title:"Symfony bundle",permalink:"/docs/6.0/symfony-bundle"},next:{title:"Universal service providers",permalink:"/docs/6.0/universal-service-providers"}},p={},s=[{value:"Installation",id:"installation",level:2},{value:"Configuring CSRF protection",id:"configuring-csrf-protection",level:2},{value:"Use the api middleware",id:"use-the-api-middleware",level:3},{value:"Disable CSRF for the /graphql route",id:"disable-csrf-for-the-graphql-route",level:3},{value:"Configuring your GraphQL client",id:"configuring-your-graphql-client",level:3},{value:"Adding GraphQL DevTools",id:"adding-graphql-devtools",level:2},{value:"Troubleshooting HTTP 419 errors",id:"troubleshooting-http-419-errors",level:2}],g={toc:s},h="wrapper";function u(e){let{components:a,...n}=e;return(0,r.yg)(h,(0,t.A)({},g,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Be advised!")," This documentation will be removed in a future release. For current and up-to-date Laravel extension specific documentation, please see the ",(0,r.yg)("a",{href:"https://github.com/thecodingmachine/graphqlite-laravel"},"Github repository"),"."),(0,r.yg)("p",null,"The GraphQLite-Laravel package is compatible with ",(0,r.yg)("strong",{parentName:"p"},"Laravel 5.7+"),", ",(0,r.yg)("strong",{parentName:"p"},"Laravel 6.x")," and ",(0,r.yg)("strong",{parentName:"p"},"Laravel 7.x"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"Open a terminal in your current project directory and run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require thecodingmachine/graphqlite-laravel\n")),(0,r.yg)("p",null,"If you want to publish the configuration (in order to edit it), run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},'$ php artisan vendor:publish --provider="TheCodingMachine\\GraphQLite\\Laravel\\Providers\\GraphQLiteServiceProvider"\n')),(0,r.yg)("p",null,"You can then configure the library by editing ",(0,r.yg)("inlineCode",{parentName:"p"},"config/graphqlite.php"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," 'App\\\\Http\\\\Controllers',\n 'types' => 'App\\\\',\n 'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,\n 'uri' => env('GRAPHQLITE_URI', '/graphql'),\n 'middleware' => ['web'],\n 'guard' => ['web'],\n];\n")),(0,r.yg)("p",null,"The debug parameters are detailed in the ",(0,r.yg)("a",{parentName:"p",href:"https://webonyx.github.io/graphql-php/error-handling/"},"documentation of the Webonyx GraphQL library"),"\nwhich is used internally by GraphQLite."),(0,r.yg)("h2",{id:"configuring-csrf-protection"},"Configuring CSRF protection"),(0,r.yg)("div",{class:"alert alert--warning"},"By default, the ",(0,r.yg)("code",null,"/graphql")," route is placed under ",(0,r.yg)("code",null,"web")," middleware group which requires a",(0,r.yg)("a",{href:"https://laravel.com/docs/6.x/csrf"},"CSRF token"),"."),(0,r.yg)("p",null,"You have 3 options:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"Use the ",(0,r.yg)("inlineCode",{parentName:"li"},"api")," middleware"),(0,r.yg)("li",{parentName:"ul"},"Disable CSRF for GraphQL routes"),(0,r.yg)("li",{parentName:"ul"},"or configure your GraphQL client to pass the ",(0,r.yg)("inlineCode",{parentName:"li"},"X-CSRF-TOKEN")," with every GraphQL query")),(0,r.yg)("h3",{id:"use-the-api-middleware"},"Use the ",(0,r.yg)("inlineCode",{parentName:"h3"},"api")," middleware"),(0,r.yg)("p",null,"If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the\n",(0,r.yg)("inlineCode",{parentName:"p"},"api")," middleware instead of the ",(0,r.yg)("inlineCode",{parentName:"p"},"web")," middleware:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php",metastring:'title="config/graphqlite.php"',title:'"config/graphqlite.php"'}," ['api'],\n 'guard' => ['api'],\n];\n")),(0,r.yg)("h3",{id:"disable-csrf-for-the-graphql-route"},"Disable CSRF for the /graphql route"),(0,r.yg)("p",null,"If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications\n(through CORS headers), you need to disable the CSRF token."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," to ",(0,r.yg)("inlineCode",{parentName:"p"},"$except")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"app/Http/Middleware/VerifyCsrfToken.php"),"."),(0,r.yg)("h3",{id:"configuring-your-graphql-client"},"Configuring your GraphQL client"),(0,r.yg)("p",null,"If you are planning to use ",(0,r.yg)("inlineCode",{parentName:"p"},"graphql")," only from your website domain, then the safest way is to keep CSRF enabled and\nconfigure your GraphQL JS client to pass the CSRF headers on any graphql request."),(0,r.yg)("p",null,"The way you do this depends on the Javascript GraphQL client you are using."),(0,r.yg)("p",null,"Assuming you are using ",(0,r.yg)("a",{parentName:"p",href:"https://www.apollographql.com/docs/link/links/http/"},"Apollo"),", you need to be sure that Apollo passes the token\nback to Laravel on every request."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="Sample Apollo client setup with CSRF support"',title:'"Sample',Apollo:!0,client:!0,setup:!0,with:!0,CSRF:!0,'support"':!0},"import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';\n\nconst httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });\n\nconst authLink = new ApolloLink((operation, forward) => {\n // Retrieve the authorization token from local storage.\n const token = localStorage.getItem('auth_token');\n\n // Get the XSRF-TOKEN that is set by Laravel on each request\n var cookieValue = document.cookie.replace(/(?:(?:^|.*;\\s*)XSRF-TOKEN\\s*\\=\\s*([^;]*).*$)|^.*$/, \"$1\");\n\n // Use the setContext method to set the X-CSRF-TOKEN header back.\n operation.setContext({\n headers: {\n 'X-CSRF-TOKEN': cookieValue\n }\n });\n\n // Call the next link in the middleware chain.\n return forward(operation);\n});\n\nconst client = new ApolloClient({\n link: authLink.concat(httpLink), // Chain it with the HttpLink\n cache: new InMemoryCache()\n});\n")),(0,r.yg)("h2",{id:"adding-graphql-devtools"},"Adding GraphQL DevTools"),(0,r.yg)("p",null,"GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.\nTo integrate a web UI to query your GraphQL endpoint with your Laravel installation,\nwe recommend installing ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/mll-lab/laravel-graphql-playground"},"GraphQL Playground")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require mll-lab/laravel-graphql-playground\n")),(0,r.yg)("p",null,"By default, the playground will be available at ",(0,r.yg)("inlineCode",{parentName:"p"},"/graphql-playground"),"."),(0,r.yg)("p",null,"Or you can install ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/XKojiMedia/laravel-altair-graphql"},"Altair GraphQL Client")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require xkojimedia/laravel-altair-graphql\n")),(0,r.yg)("p",null,"You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (",(0,r.yg)("inlineCode",{parentName:"p"},"'/graphql'")," by default)."),(0,r.yg)("h2",{id:"troubleshooting-http-419-errors"},"Troubleshooting HTTP 419 errors"),(0,r.yg)("p",null,"If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your\nCSRF token. Please check again ",(0,r.yg)("a",{parentName:"p",href:"#configuring-csrf-protection"},"the paragraph dedicated to CSRF configuration"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/cd25a595.2ff2dc72.js b/assets/js/cd25a595.ad485f50.js similarity index 89% rename from assets/js/cd25a595.2ff2dc72.js rename to assets/js/cd25a595.ad485f50.js index 2b7936fe2a..ae861218d1 100644 --- a/assets/js/cd25a595.2ff2dc72.js +++ b/assets/js/cd25a595.ad485f50.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2113],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),u=a(56347),i=a(57485),s=a(31682),p=a(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function c(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??d(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function f(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=c(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,s]=h({queryString:a,groupId:n}),[d,f]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),g=(()=>{const e=i??d;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{g&&u(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),s(e),f(e)}),[s,f,l]),tabValues:l}}var g=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:u,selectValue:i,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),c=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==u&&(d(t),i(n))},m=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:c},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":u===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},26183:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>s,toc:()=>d});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const u={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},i=void 0,s={unversionedId:"file-uploads",id:"version-4.2/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-4.2/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/4.2/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/file-uploads.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"version-4.2/docs",previous:{title:"Prefetching records",permalink:"/docs/4.2/prefetch-method"},next:{title:"Pagination",permalink:"/docs/4.2/pagination"}},p={},d=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],c={toc:d},m="wrapper";function h(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,r.yg)("p",null,"You must start by installing this package:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,r.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,r.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,r.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,r.yg)("p",null,"In order to use this, you must first be sure that the ",(0,r.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,r.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,r.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,r.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")))),(0,r.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,r.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2113],{19365:(e,t,a)=>{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>I});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),u=a(56347),i=a(57485),s=a(31682),p=a(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,s.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:a}=e;const n=(0,u.W6)(),l=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,i.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function f(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,u]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:l}))),[i,s]=h({queryString:a,groupId:n}),[c,f]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,p.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),g=(()=>{const e=i??c;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{g&&u(g)}),[g]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);u(e),s(e),f(e)}),[s,f,l]),tabValues:l}}var g=a(92303);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:u,selectValue:i,tabValues:s}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=s[a].value;n!==u&&(c(t),i(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},s.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:u===t?0:-1,"aria-selected":u===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:d},o,{className:(0,l.A)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":u===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function w(e){const t=f(e);return r.createElement("div",{className:(0,l.A)("tabs-container",y.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function I(e){const t=(0,g.A)();return r.createElement(w,(0,n.A)({key:String(t)},e))}},26183:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>i,default:()=>h,frontMatter:()=>u,metadata:()=>s,toc:()=>c});var n=a(58168),r=(a(96540),a(15680)),l=(a(67443),a(11470)),o=a(19365);const u={id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},i=void 0,s={unversionedId:"file-uploads",id:"version-4.2/file-uploads",title:"File uploads",description:"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed",source:"@site/versioned_docs/version-4.2/file-uploads.mdx",sourceDirName:".",slug:"/file-uploads",permalink:"/docs/4.2/file-uploads",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/file-uploads.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"file-uploads",title:"File uploads",sidebar_label:"File uploads"},sidebar:"version-4.2/docs",previous:{title:"Prefetching records",permalink:"/docs/4.2/prefetch-method"},next:{title:"Pagination",permalink:"/docs/4.2/pagination"}},p={},c=[{value:"Installation",id:"installation",level:2},{value:"If you are using the Symfony bundle",id:"if-you-are-using-the-symfony-bundle",level:3},{value:"If you are using a PSR-15 compatible framework",id:"if-you-are-using-a-psr-15-compatible-framework",level:3},{value:"If you are using another framework not compatible with PSR-15",id:"if-you-are-using-another-framework-not-compatible-with-psr-15",level:3},{value:"Usage",id:"usage",level:2}],d={toc:c},m="wrapper";function h(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQL does not support natively the notion of file uploads, but an extension to the GraphQL protocol was proposed\nto add support for ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec"},"multipart requests"),"."),(0,r.yg)("h2",{id:"installation"},"Installation"),(0,r.yg)("p",null,"GraphQLite supports this extension through the use of the ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"Ecodev/graphql-upload")," library."),(0,r.yg)("p",null,"You must start by installing this package:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-console"},"$ composer require ecodev/graphql-upload\n")),(0,r.yg)("h3",{id:"if-you-are-using-the-symfony-bundle"},"If you are using the Symfony bundle"),(0,r.yg)("p",null,"If you are using our Symfony bundle, the file upload middleware is managed by the bundle. You have nothing to do\nand can start using it right away."),(0,r.yg)("h3",{id:"if-you-are-using-a-psr-15-compatible-framework"},"If you are using a PSR-15 compatible framework"),(0,r.yg)("p",null,"In order to use this, you must first be sure that the ",(0,r.yg)("inlineCode",{parentName:"p"},"ecodev/graphql-upload")," PSR-15 middleware is part of your middleware pipe."),(0,r.yg)("p",null,"Simply add ",(0,r.yg)("inlineCode",{parentName:"p"},"GraphQL\\Upload\\UploadMiddleware")," to your middleware pipe."),(0,r.yg)("h3",{id:"if-you-are-using-another-framework-not-compatible-with-psr-15"},"If you are using another framework not compatible with PSR-15"),(0,r.yg)("p",null,"Please check the Ecodev/graphql-upload library ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/Ecodev/graphql-upload"},"documentation"),"\nfor more information on how to integrate it in your framework."),(0,r.yg)("h2",{id:"usage"},"Usage"),(0,r.yg)("p",null,"To handle an uploaded file, you type-hint against the PSR-7 ",(0,r.yg)("inlineCode",{parentName:"p"},"UploadedFileInterface"),":"),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(o.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n #[Mutation]\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n"))),(0,r.yg)(o.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Mutation\n */\n public function saveDocument(string $name, UploadedFileInterface $file): Document\n {\n // Some code that saves the document.\n $file->moveTo($someDir);\n }\n}\n")))),(0,r.yg)("p",null,"Of course, you need to use a GraphQL client that is compatible with multipart requests. See ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/jaydenseric/graphql-multipart-request-spec#client"},"jaydenseric/graphql-multipart-request-spec")," for a list of compatible clients."),(0,r.yg)("p",null,"The GraphQL client must send the file using the Upload type."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation upload($file: Upload!) {\n upload(file: $file)\n}\n")))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/cd30f404.204da6fe.js b/assets/js/cd30f404.69774811.js similarity index 93% rename from assets/js/cd30f404.204da6fe.js rename to assets/js/cd30f404.69774811.js index 4de4ad73f1..988452cef6 100644 --- a/assets/js/cd30f404.204da6fe.js +++ b/assets/js/cd30f404.69774811.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9661],{19365:(e,a,n)=>{n.d(a,{A:()=>i});var t=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:n,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},a)}},11470:(e,a,n)=>{n.d(a,{A:()=>P});var t=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:n,attributes:t,default:r}}=e;return{value:a,label:n,attributes:t,default:r}}))}function c(e){const{values:a,children:n}=e;return(0,r.useMemo)((()=>{const e=a??d(n);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,n])}function g(e){let{value:a,tabValues:n}=e;return n.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:n}=e;const t=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:n}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:a,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(t.location.search);a.set(l,e),t.replace({...t.location,search:a.toString()})}),[l,t])]}function m(e){const{defaultValue:a,queryString:n=!1,groupId:t}=e,l=c(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const t=n.find((e=>e.default))??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:n,groupId:t}),[d,m]=function(e){let{groupId:a}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(a),[t,l]=(0,p.Dv)(n);return[t,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:t}),y=(()=>{const e=s??d;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:d}=(0,i.a_)(),c=e=>{const a=e.currentTarget,n=p.indexOf(a),t=u[n].value;t!==o&&(d(a),s(t))},g=e=>{let a=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;a=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;a=p[n]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},a)},u.map((e=>{let{value:a,label:n,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:c},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),n??a)})))}function b(e){let{lazy:a,children:n,selectedValue:t}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==t}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,t.A)({},e,a)),r.createElement(b,(0,t.A)({},e,a)))}function P(e){const a=(0,y.A)();return r.createElement(w,(0,t.A)({key:String(a)},e))}},6638:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>d});var t=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features",original_id:"laravel-package-advanced"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-4.1/laravel-package-advanced",title:"Laravel package: advanced usage",description:"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel.",source:"@site/versioned_docs/version-4.1/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/4.1/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/laravel-package-advanced.mdx",tags:[],version:"4.1",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features",original_id:"laravel-package-advanced"},sidebar:"version-4.1/docs",previous:{title:"Symfony specific features",permalink:"/docs/4.1/symfony-bundle-advanced"},next:{title:"Internals",permalink:"/docs/4.1/internals"}},p={},d=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3}],c={toc:d},g="wrapper";function h(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},c,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"```php\nclass User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n```\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'```php\n/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n```\n'))))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[9661],{19365:(e,a,n)=>{n.d(a,{A:()=>i});var t=n(96540),r=n(20053);const l={tabItem:"tabItem_Ymn6"};function i(e){let{children:a,hidden:n,className:i}=e;return t.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,i),hidden:n},a)}},11470:(e,a,n)=>{n.d(a,{A:()=>P});var t=n(58168),r=n(96540),l=n(20053),i=n(23104),o=n(56347),s=n(57485),u=n(31682),p=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:a}=e;return!!a&&"object"==typeof a&&"value"in a}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:a,label:n,attributes:t,default:r}}=e;return{value:a,label:n,attributes:t,default:r}}))}function d(e){const{values:a,children:n}=e;return(0,r.useMemo)((()=>{const e=a??c(n);return function(e){const a=(0,u.X)(e,((e,a)=>e.value===a.value));if(a.length>0)throw new Error(`Docusaurus error: Duplicate values "${a.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[a,n])}function g(e){let{value:a,tabValues:n}=e;return n.some((e=>e.value===a))}function h(e){let{queryString:a=!1,groupId:n}=e;const t=(0,o.W6)(),l=function(e){let{queryString:a=!1,groupId:n}=e;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:a,groupId:n});return[(0,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const a=new URLSearchParams(t.location.search);a.set(l,e),t.replace({...t.location,search:a.toString()})}),[l,t])]}function m(e){const{defaultValue:a,queryString:n=!1,groupId:t}=e,l=d(e),[i,o]=(0,r.useState)((()=>function(e){let{defaultValue:a,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(a){if(!g({value:a,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${a}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return a}const t=n.find((e=>e.default))??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:a,tabValues:l}))),[s,u]=h({queryString:n,groupId:t}),[c,m]=function(e){let{groupId:a}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(a),[t,l]=(0,p.Dv)(n);return[t,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:t}),y=(()=>{const e=s??c;return g({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&o(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);o(e),u(e),m(e)}),[u,m,l]),tabValues:l}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function v(e){let{className:a,block:n,selectedValue:o,selectValue:s,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const a=e.currentTarget,n=p.indexOf(a),t=u[n].value;t!==o&&(c(a),s(t))},g=e=>{let a=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=p.indexOf(e.currentTarget)+1;a=p[n]??p[0];break}case"ArrowLeft":{const n=p.indexOf(e.currentTarget)-1;a=p[n]??p[p.length-1];break}}a?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":n},a)},u.map((e=>{let{value:a,label:n,attributes:i}=e;return r.createElement("li",(0,t.A)({role:"tab",tabIndex:o===a?0:-1,"aria-selected":o===a,key:a,ref:e=>p.push(e),onKeyDown:g,onClick:d},i,{className:(0,l.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":o===a})}),n??a)})))}function b(e){let{lazy:a,children:n,selectedValue:t}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(a){const e=l.find((e=>e.props.value===t));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,a)=>(0,r.cloneElement)(e,{key:a,hidden:e.props.value!==t}))))}function w(e){const a=m(e);return r.createElement("div",{className:(0,l.A)("tabs-container",f.tabList)},r.createElement(v,(0,t.A)({},e,a)),r.createElement(b,(0,t.A)({},e,a)))}function P(e){const a=(0,y.A)();return r.createElement(w,(0,t.A)({key:String(a)},e))}},6638:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>p,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>u,toc:()=>c});var t=n(58168),r=(n(96540),n(15680)),l=(n(67443),n(11470)),i=n(19365);const o={id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features",original_id:"laravel-package-advanced"},s=void 0,u={unversionedId:"laravel-package-advanced",id:"version-4.1/laravel-package-advanced",title:"Laravel package: advanced usage",description:"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel.",source:"@site/versioned_docs/version-4.1/laravel-package-advanced.mdx",sourceDirName:".",slug:"/laravel-package-advanced",permalink:"/docs/4.1/laravel-package-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.1/laravel-package-advanced.mdx",tags:[],version:"4.1",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"laravel-package-advanced",title:"Laravel package: advanced usage",sidebar_label:"Laravel specific features",original_id:"laravel-package-advanced"},sidebar:"version-4.1/docs",previous:{title:"Symfony specific features",permalink:"/docs/4.1/symfony-bundle-advanced"},next:{title:"Internals",permalink:"/docs/4.1/internals"}},p={},c=[{value:"Support for Laravel validation rules",id:"support-for-laravel-validation-rules",level:2},{value:"Support for pagination",id:"support-for-pagination",level:2},{value:"Simple paginator",id:"simple-paginator",level:3},{value:"Using GraphQLite with Eloquent efficiently",id:"using-graphqlite-with-eloquent-efficiently",level:2},{value:"Pitfalls to avoid with Eloquent",id:"pitfalls-to-avoid-with-eloquent",level:3}],d={toc:c},g="wrapper";function h(e){let{components:a,...n}=e;return(0,r.yg)(g,(0,t.A)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel."),(0,r.yg)("h2",{id:"support-for-laravel-validation-rules"},"Support for Laravel validation rules"),(0,r.yg)("p",null,"The GraphQLite Laravel package comes with a special ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation to use Laravel validation rules in your\ninput types."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n #[Mutation]\n public function createUser(\n #[Validate("email|unique:users")]\n string $email,\n #[Validate("gte:8")]\n string $password\n ): User\n {\n // ...\n }\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'use TheCodingMachine\\GraphQLite\\Laravel\\Annotations\\Validate;\n\nclass MyController\n{\n /**\n * @Mutation\n * @Validate(for="$email", rule="email|unique:users")\n * @Validate(for="$password", rule="gte:8")\n */\n public function createUser(string $email, string $password): User\n {\n // ...\n }\n}\n')))),(0,r.yg)("p",null,"You can use the ",(0,r.yg)("inlineCode",{parentName:"p"},"@Validate")," annotation in any query / mutation / field / factory / decorator."),(0,r.yg)("p",null,'If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:'),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-json"},'{\n "errors": [\n {\n "message": "The email must be a valid email address.",\n "extensions": {\n "argument": "email",\n "category": "Validate"\n }\n },\n {\n "message": "The password must be greater than or equal 8 characters.",\n "extensions": {\n "argument": "password",\n "category": "Validate"\n }\n }\n ]\n}\n')),(0,r.yg)("p",null,"You can use any validation rule described in ",(0,r.yg)("a",{parentName:"p",href:"https://laravel.com/docs/6.x/validation#available-validation-rules"},"the Laravel documentation")),(0,r.yg)("h2",{id:"support-for-pagination"},"Support for pagination"),(0,r.yg)("p",null,"In your query, if you explicitly return an object that extends the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\LengthAwarePaginator"),' class,\nthe query result will be wrapped in a "paginator" type.'),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\LengthAwarePaginator\n {\n return Product::paginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"Notice that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the method return type MUST BE ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")," or a class extending ",(0,r.yg)("inlineCode",{parentName:"li"},"Illuminate\\Pagination\\LengthAwarePaginator")),(0,r.yg)("li",{parentName:"ul"},"you MUST add a ",(0,r.yg)("inlineCode",{parentName:"li"},"@return")," statement to help GraphQLite find the type of the list")),(0,r.yg)("p",null,"Once this is done, you can get plenty of useful information about this page:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'products {\n items { # The items for the selected page\n id\n name\n }\n totalCount # The total count of items.\n lastPage # Get the page number of the last available page.\n firstItem # Get the "index" of the first item being paginated.\n lastItem # Get the "index" of the last item being paginated.\n hasMorePages # Determine if there are more items in the data source.\n perPage # Get the number of items shown per page.\n hasPages # Determine if there are enough items to split into multiple pages.\n currentPage # Determine the current page being paginated.\n isEmpty # Determine if the list of items is empty or not.\n isNotEmpty # Determine if the list of items is not empty.\n}\n')),(0,r.yg)("div",{class:"alert alert--warning"},"Be sure to type hint on the class (",(0,r.yg)("code",null,"Illuminate\\Pagination\\LengthAwarePaginator"),") and not on the interface (",(0,r.yg)("code",null,"Illuminate\\Contracts\\Pagination\\LengthAwarePaginator"),"). The interface itself is not iterable (it does not extend ",(0,r.yg)("code",null,"Traversable"),") and therefore, GraphQLite will refuse to iterate over it."),(0,r.yg)("h3",{id:"simple-paginator"},"Simple paginator"),(0,r.yg)("p",null,"Note: if you are using ",(0,r.yg)("inlineCode",{parentName:"p"},"simplePaginate")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"paginate"),", you can type hint on the ",(0,r.yg)("inlineCode",{parentName:"p"},"Illuminate\\Pagination\\Paginator")," class."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class MyController\n{\n /**\n * @Query\n * @return Product[]\n */\n public function products(): Illuminate\\Pagination\\Paginator\n {\n return Product::simplePaginate(15);\n }\n}\n")))),(0,r.yg)("p",null,"The behaviour will be exactly the same except you will be missing the ",(0,r.yg)("inlineCode",{parentName:"p"},"totalCount")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"lastPage")," fields."),(0,r.yg)("h2",{id:"using-graphqlite-with-eloquent-efficiently"},"Using GraphQLite with Eloquent efficiently"),(0,r.yg)("p",null,"In GraphQLite, you are supposed to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on each getter."),(0,r.yg)("p",null,"Eloquent uses PHP magic properties to expose your database records.\nBecause Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters."),(0,r.yg)("p",null,"So we need to find a workaround. GraphQLite comes with a ",(0,r.yg)("inlineCode",{parentName:"p"},"@MagicField")," annotation to help you\nworking with magic properties."),(0,r.yg)(l.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'#[Type]\n#[MagicField(name: "id", outputType: "ID!")]\n#[MagicField(name: "name", phpType: "string")]\n#[MagicField(name: "categories", phpType: "Category[]")]\nclass Product extends Model\n{\n}\n'))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Type()\n * @MagicField(name="id", outputType="ID!")\n * @MagicField(name="name", phpType="string")\n * @MagicField(name="categories", phpType="Category[]")\n */\nclass Product extends Model\n{\n}\n')))),(0,r.yg)("p",null,'Please note that since the properties are "magic", they don\'t have a type. Therefore,\nyou need to pass either the "outputType" attribute with the GraphQL type matching the property,\nor the "phpType" attribute with the PHP type matching the property.'),(0,r.yg)("h3",{id:"pitfalls-to-avoid-with-eloquent"},"Pitfalls to avoid with Eloquent"),(0,r.yg)("p",null,"When designing relationships in Eloquent, you write a method to expose that relationship this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"class User extends Model\n{\n /**\n * Get the phone record associated with the user.\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n")),(0,r.yg)("p",null,"It would be tempting to put a ",(0,r.yg)("inlineCode",{parentName:"p"},"@Field")," annotation on the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method, but this will not work. Indeed,\nthe ",(0,r.yg)("inlineCode",{parentName:"p"},"phone()")," method does not return a ",(0,r.yg)("inlineCode",{parentName:"p"},"App\\Phone")," object. It is the ",(0,r.yg)("inlineCode",{parentName:"p"},"phone")," magic property that returns it."),(0,r.yg)("p",null,"In short:"),(0,r.yg)("div",{class:"alert alert--danger"},"This does not work:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"```php\nclass User extends Model\n{\n /**\n * @Field\n */\n public function phone()\n {\n return $this->hasOne('App\\Phone');\n }\n}\n```\n"))),(0,r.yg)("div",{class:"alert alert--success"},"This works:",(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},'```php\n/**\n * @MagicField(name="phone", phpType="App\\\\Phone")\n */\nclass User extends Model\n{\n public function phone()\n {\n return $this->hasOne(\'App\\Phone\');\n }\n}\n```\n'))))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/cd699560.967c131e.js b/assets/js/cd699560.d49156fe.js similarity index 93% rename from assets/js/cd699560.967c131e.js rename to assets/js/cd699560.d49156fe.js index 4a60897183..97b1e3fbb9 100644 --- a/assets/js/cd699560.967c131e.js +++ b/assets/js/cd699560.d49156fe.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5281],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),o=n(20053),i=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function g(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function y(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=d(e),[i,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[s,u]=m({queryString:n,groupId:a}),[p,y]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),h=(()=>{const e=s??p;return g({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{h&&l(h)}),[h]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var h=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),s(a))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},i,{className:(0,o.A)("tabs__item",b.tabItem,i?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function P(e){const t=y(e);return r.createElement("div",{className:(0,o.A)("tabs-container",b.tabList)},r.createElement(f,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,h.A)();return r.createElement(P,(0,a.A)({key:String(t)},e))}},31017:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),o=(n(67443),n(11470)),i=n(19365);const l={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},s=void 0,u={unversionedId:"doctrine-annotations-attributes",id:"version-4.2/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-4.2/doctrine-annotations-attributes.mdx",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/4.2/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/doctrine-annotations-attributes.mdx",tags:[],version:"4.2",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},sidebar:"version-4.2/docs",previous:{title:"Migrating",permalink:"/docs/4.2/migrating"},next:{title:"Annotations reference",permalink:"/docs/4.2/annotations-reference"}},c={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(g,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,r.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in a future release."),(0,r.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support. This was the purpose of the ',(0,r.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,r.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"Please note that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,r.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,r.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,r.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("strong",null,"Heads up!"),(0,r.yg)("p",null,"Some IDEs provide support for Doctrine annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",null,"PhpStorm via the ",(0,r.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,r.yg)("li",null,"Eclipse via the ",(0,r.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,r.yg)("li",null,"Netbeans has native support")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"We strongly recommend using an IDE that has Doctrine annotations support.\n"))),(0,r.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,r.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,r.yg)("p",null,"The same code can be written this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,r.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,r.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,r.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,r.yg)("p",null,"They support the same attributes too."),(0,r.yg)("p",null,"A few notable differences:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,r.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,r.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,r.yg)("p",null,"Let's take an example with the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/autowiring"},(0,r.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')))))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[5281],{19365:(e,t,n)=>{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),o=n(20053),i=n(23104),l=n(56347),s=n(57485),u=n(31682),c=n(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function g(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.W6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function h(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=d(e),[i,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!g({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[s,u]=m({queryString:n,groupId:a}),[p,h]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),y=(()=>{const e=s??p;return g({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{y&&l(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!g({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),h(e)}),[u,h,o]),tabValues:o}}var y=n(92303);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function f(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.a_)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),s(a))},g=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:g,onClick:d},i,{className:(0,o.A)("tabs__item",b.tabItem,i?.className,{"tabs__item--active":l===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function P(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",b.tabList)},r.createElement(f,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,y.A)();return r.createElement(P,(0,a.A)({key:String(t)},e))}},31017:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>m,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(58168),r=(n(96540),n(15680)),o=(n(67443),n(11470)),i=n(19365);const l={id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},s=void 0,u={unversionedId:"doctrine-annotations-attributes",id:"version-4.2/doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",description:"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+).",source:"@site/versioned_docs/version-4.2/doctrine-annotations-attributes.mdx",sourceDirName:".",slug:"/doctrine-annotations-attributes",permalink:"/docs/4.2/doctrine-annotations-attributes",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-4.2/doctrine-annotations-attributes.mdx",tags:[],version:"4.2",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"doctrine-annotations-attributes",title:"Doctrine annotations VS PHP8 attributes",sidebar_label:"Annotations VS Attributes"},sidebar:"version-4.2/docs",previous:{title:"Migrating",permalink:"/docs/4.2/migrating"},next:{title:"Annotations reference",permalink:"/docs/4.2/annotations-reference"}},c={},p=[{value:"Doctrine annotations",id:"doctrine-annotations",level:2},{value:"PHP 8 attributes",id:"php-8-attributes",level:2}],d={toc:p},g="wrapper";function m(e){let{components:t,...n}=e;return(0,r.yg)(g,(0,a.A)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,"GraphQLite is heavily relying on the concept of annotations (also called attributes in PHP 8+)."),(0,r.yg)("h2",{id:"doctrine-annotations"},"Doctrine annotations"),(0,r.yg)("div",{class:"alert alert--warning"},(0,r.yg)("strong",null,"Deprecated!")," Doctrine annotations are deprecated in favor of native PHP 8 attributes. Support will be dropped in a future release."),(0,r.yg)("p",null,'Historically, attributes were not available in PHP and PHP developers had to "trick" PHP to get annotation support. This was the purpose of the ',(0,r.yg)("a",{parentName:"p",href:"https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html"},"doctrine/annotation")," library."),(0,r.yg)("p",null,"Using Doctrine annotations, you write annotations in your docblocks:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n/**\n * @Type\n */\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"Please note that:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The annotation is added in a ",(0,r.yg)("strong",{parentName:"li"},"docblock"),' (a comment starting with "',(0,r.yg)("inlineCode",{parentName:"li"},"/**"),'")'),(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"Type")," part is actually a class. It must be declared in the ",(0,r.yg)("inlineCode",{parentName:"li"},"use")," statements at the top of your file.")),(0,r.yg)("div",{class:"alert alert--info"},(0,r.yg)("strong",null,"Heads up!"),(0,r.yg)("p",null,"Some IDEs provide support for Doctrine annotations:"),(0,r.yg)("ul",null,(0,r.yg)("li",null,"PhpStorm via the ",(0,r.yg)("a",{href:"https://plugins.jetbrains.com/plugin/7320-php-annotations"},"PHP Annotations Plugin")),(0,r.yg)("li",null,"Eclipse via the ",(0,r.yg)("a",{href:"https://marketplace.eclipse.org/content/symfony-plugin"},"Symfony 2 Plugin")),(0,r.yg)("li",null,"Netbeans has native support")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"},"We strongly recommend using an IDE that has Doctrine annotations support.\n"))),(0,r.yg)("h2",{id:"php-8-attributes"},"PHP 8 attributes"),(0,r.yg)("p",null,'Starting with PHP 8, PHP got native annotations support. They are actually called "attributes" in the PHP world.'),(0,r.yg)("p",null,"The same code can be written this way:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use TheCodingMachine\\GraphQLite\\Annotations\\Type;\n\n#[Type]\nclass MyType\n{\n}\n")),(0,r.yg)("p",null,"GraphQLite v4.1+ has support for PHP 8 attributes."),(0,r.yg)("p",null,"The Doctrine annotation class and the PHP 8 attribute class is ",(0,r.yg)("strong",{parentName:"p"},"the same")," (so you will be using the same ",(0,r.yg)("inlineCode",{parentName:"p"},"use")," statement at the top of your file)."),(0,r.yg)("p",null,"They support the same attributes too."),(0,r.yg)("p",null,"A few notable differences:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"PHP 8 attributes do not support nested attributes (unlike Doctrine annotations). This means there is no equivalent to the ",(0,r.yg)("inlineCode",{parentName:"li"},"annotations")," attribute of ",(0,r.yg)("inlineCode",{parentName:"li"},"@MagicField")," and ",(0,r.yg)("inlineCode",{parentName:"li"},"@SourceField"),"."),(0,r.yg)("li",{parentName:"ul"},'PHP 8 attributes can be written at the parameter level. Any attribute targeting a "parameter" must be written at the parameter level.')),(0,r.yg)("p",null,"Let's take an example with the ",(0,r.yg)("a",{parentName:"p",href:"/docs/4.2/autowiring"},(0,r.yg)("inlineCode",{parentName:"a"},"#Autowire")," attribute"),":"),(0,r.yg)(o.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,r.yg)(i.A,{value:"php8",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"#[Field]\npublic function getProduct(#[Autowire] ProductRepository $productRepository) : Product {\n //...\n}\n"))),(0,r.yg)(i.A,{value:"php7",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},'/**\n * @Field\n * @Autowire(for="$productRepository")\n */\npublic function getProduct(ProductRepository $productRepository) : Product {\n //...\n}\n')))))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/cddcd4e6.f9de7ce3.js b/assets/js/cddcd4e6.7605ffd6.js similarity index 89% rename from assets/js/cddcd4e6.f9de7ce3.js rename to assets/js/cddcd4e6.7605ffd6.js index 1a2a40f178..06a857a4ec 100644 --- a/assets/js/cddcd4e6.f9de7ce3.js +++ b/assets/js/cddcd4e6.7605ffd6.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4116],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function p(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function m(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??p(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function d(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function g(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=m(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!d({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=g({queryString:t,groupId:a}),[p,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??p;return d({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!d({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,u.a_)(),m=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(p(n),i(a))},d=e=>{let n=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:d,onClick:m},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},89848:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-3.0/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony.",source:"@site/versioned_docs/version-3.0/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/3.0/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/symfony-bundle-advanced.mdx",tags:[],version:"3.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"}},c={},p=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],m={toc:p},d="wrapper";function g(e){let{components:n,...t}=e;return(0,l.yg)(d,(0,a.A)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4116],{19365:(e,n,t)=>{t.d(n,{A:()=>u});var a=t(96540),l=t(20053);const r={tabItem:"tabItem_Ymn6"};function u(e){let{children:n,hidden:t,className:u}=e;return a.createElement("div",{role:"tabpanel",className:(0,l.A)(r.tabItem,u),hidden:t},n)}},11470:(e,n,t)=>{t.d(n,{A:()=>N});var a=t(58168),l=t(96540),r=t(20053),u=t(23104),o=t(56347),i=t(57485),s=t(31682),c=t(89466);function m(e){return function(e){return l.Children.map(e,(e=>{if(!e||(0,l.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:n,label:t,attributes:a,default:l}}=e;return{value:n,label:t,attributes:a,default:l}}))}function p(e){const{values:n,children:t}=e;return(0,l.useMemo)((()=>{const e=n??m(t);return function(e){const n=(0,s.X)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[n,t])}function g(e){let{value:n,tabValues:t}=e;return t.some((e=>e.value===n))}function d(e){let{queryString:n=!1,groupId:t}=e;const a=(0,o.W6)(),r=function(e){let{queryString:n=!1,groupId:t}=e;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!t)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return t??null}({queryString:n,groupId:t});return[(0,i.aZ)(r),(0,l.useCallback)((e=>{if(!r)return;const n=new URLSearchParams(a.location.search);n.set(r,e),a.replace({...a.location,search:n.toString()})}),[r,a])]}function y(e){const{defaultValue:n,queryString:t=!1,groupId:a}=e,r=p(e),[u,o]=(0,l.useState)((()=>function(e){let{defaultValue:n,tabValues:t}=e;if(0===t.length)throw new Error("Docusaurus error: the component requires at least one children component");if(n){if(!g({value:n,tabValues:t}))throw new Error(`Docusaurus error: The has a defaultValue "${n}" but none of its children has the corresponding value. Available values are: ${t.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return n}const a=t.find((e=>e.default))??t[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:r}))),[i,s]=d({queryString:t,groupId:a}),[m,y]=function(e){let{groupId:n}=e;const t=function(e){return e?`docusaurus.tab.${e}`:null}(n),[a,r]=(0,c.Dv)(t);return[a,(0,l.useCallback)((e=>{t&&r.set(e)}),[t,r])]}({groupId:a}),h=(()=>{const e=i??m;return g({value:e,tabValues:r})?e:null})();(0,l.useLayoutEffect)((()=>{h&&o(h)}),[h]);return{selectedValue:u,selectValue:(0,l.useCallback)((e=>{if(!g({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);o(e),s(e),y(e)}),[s,y,r]),tabValues:r}}var h=t(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:n,block:t,selectedValue:o,selectValue:i,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:m}=(0,u.a_)(),p=e=>{const n=e.currentTarget,t=c.indexOf(n),a=s[t].value;a!==o&&(m(n),i(a))},g=e=>{let n=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const t=c.indexOf(e.currentTarget)+1;n=c[t]??c[0];break}case"ArrowLeft":{const t=c.indexOf(e.currentTarget)-1;n=c[t]??c[c.length-1];break}}n?.focus()};return l.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":t},n)},s.map((e=>{let{value:n,label:t,attributes:u}=e;return l.createElement("li",(0,a.A)({role:"tab",tabIndex:o===n?0:-1,"aria-selected":o===n,key:n,ref:e=>c.push(e),onKeyDown:g,onClick:p},u,{className:(0,r.A)("tabs__item",f.tabItem,u?.className,{"tabs__item--active":o===n})}),t??n)})))}function v(e){let{lazy:n,children:t,selectedValue:a}=e;const r=(Array.isArray(t)?t:[t]).filter(Boolean);if(n){const e=r.find((e=>e.props.value===a));return e?(0,l.cloneElement)(e,{className:"margin-top--md"}):null}return l.createElement("div",{className:"margin-top--md"},r.map(((e,n)=>(0,l.cloneElement)(e,{key:n,hidden:e.props.value!==a}))))}function q(e){const n=y(e);return l.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},l.createElement(b,(0,a.A)({},e,n)),l.createElement(v,(0,a.A)({},e,n)))}function N(e){const n=(0,h.A)();return l.createElement(q,(0,a.A)({key:String(n)},e))}},89848:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>d,frontMatter:()=>o,metadata:()=>s,toc:()=>m});var a=t(58168),l=(t(96540),t(15680)),r=(t(67443),t(11470)),u=t(19365);const o={id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"},i=void 0,s={unversionedId:"symfony-bundle-advanced",id:"version-3.0/symfony-bundle-advanced",title:"Symfony bundle: advanced usage",description:"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony.",source:"@site/versioned_docs/version-3.0/symfony-bundle-advanced.mdx",sourceDirName:".",slug:"/symfony-bundle-advanced",permalink:"/docs/3.0/symfony-bundle-advanced",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-3.0/symfony-bundle-advanced.mdx",tags:[],version:"3.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"symfony-bundle-advanced",title:"Symfony bundle: advanced usage",sidebar_label:"Symfony specific features"}},c={},m=[{value:"Login and logout",id:"login-and-logout",level:2},{value:"Schema and request security",id:"schema-and-request-security",level:2},{value:"Login using the "login" mutation",id:"login-using-the-login-mutation",level:3},{value:"Get the current user with the "me" query",id:"get-the-current-user-with-the-me-query",level:3},{value:"Logout using the "logout" mutation",id:"logout-using-the-logout-mutation",level:3},{value:"Injecting the Request",id:"injecting-the-request",level:2}],p={toc:m},g="wrapper";function d(e){let{components:n,...t}=e;return(0,l.yg)(g,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,l.yg)("p",null,"The Symfony bundle comes with a number of features to ease the integration of GraphQLite in Symfony."),(0,l.yg)("h2",{id:"login-and-logout"},"Login and logout"),(0,l.yg)("p",null,'Out of the box, the GraphQLite bundle will expose a "login" and a "logout" mutation as well\nas a "me" query (that returns the current user).'),(0,l.yg)("p",null,'If you need to customize this behaviour, you can edit the "graphqlite.security" configuration key.'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: auto # Default setting\n enable_me: auto # Default setting\n")),(0,l.yg)("p",null,'By default, GraphQLite will enable "login" and "logout" mutations and the "me" query if the following conditions are met:'),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},'the "security" bundle is installed and configured (with a security provider and encoder)'),(0,l.yg)("li",{parentName:"ul"},'the "session" support is enabled (via the "framework.session.enabled" key).')),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: on\n")),(0,l.yg)("p",null,"By settings ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=on"),", you are stating that you explicitly want the login/logout mutations.\nIf one of the dependencies is missing, an exception is thrown (unlike in default mode where the mutations\nare silently discarded)."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n enable_login: off\n")),(0,l.yg)("p",null,"Use the ",(0,l.yg)("inlineCode",{parentName:"p"},"enable_login=off")," to disable the mutations."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n firewall_name: main # default value\n")),(0,l.yg)("p",null,'By default, GraphQLite assumes that your firewall name is "main". This is the default value used in the\nSymfony security bundle so it is likely the value you are using. If for some reason you want to use\nanother firewall, configure the name with ',(0,l.yg)("inlineCode",{parentName:"p"},"graphqlite.security.firewall_name"),"."),(0,l.yg)("h2",{id:"schema-and-request-security"},"Schema and request security"),(0,l.yg)("p",null,"You can disable the introspection of your GraphQL API (for instance in production mode) using\nthe ",(0,l.yg)("inlineCode",{parentName:"p"},"introspection")," configuration properties."),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n introspection: false\n")),(0,l.yg)("p",null,"You can set the maximum complexity and depth of your GraphQL queries using the ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_complexity"),"\nand ",(0,l.yg)("inlineCode",{parentName:"p"},"maximum_query_depth")," configuration properties"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-yaml"},"graphqlite:\n security:\n maximum_query_complexity: 314\n maximum_query_depth: 42\n")),(0,l.yg)("h3",{id:"login-using-the-login-mutation"},'Login using the "login" mutation'),(0,l.yg)("p",null,"The mutation below will log-in a user:"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},'mutation login {\n login(userName:"foo", password:"bar") {\n userName\n roles\n }\n}\n')),(0,l.yg)("h3",{id:"get-the-current-user-with-the-me-query"},'Get the current user with the "me" query'),(0,l.yg)("p",null,'Retrieving the current user is easy with the "me" query:'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n }\n}\n")),(0,l.yg)("p",null,"In Symfony, user objects implement ",(0,l.yg)("inlineCode",{parentName:"p"},"Symfony\\Component\\Security\\Core\\User\\UserInterface"),".\nThis interface is automatically mapped to a type with 2 fields:"),(0,l.yg)("ul",null,(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"userName: String!")),(0,l.yg)("li",{parentName:"ul"},(0,l.yg)("inlineCode",{parentName:"li"},"roles: [String!]!"))),(0,l.yg)("p",null,"If you want to get more fields, just add the ",(0,l.yg)("inlineCode",{parentName:"p"},"@Type")," annotation to your user class:"),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"#[Type]\nclass User implements UserInterface\n{\n #[Field]\n public function getEmail() : string\n {\n // ...\n }\n\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"/**\n * @Type\n */\nclass User implements UserInterface\n{\n /**\n * @Field\n */\n public function getEmail() : string\n {\n // ...\n }\n\n}\n")))),(0,l.yg)("p",null,"You can now query this field using an ",(0,l.yg)("a",{parentName:"p",href:"https://graphql.org/learn/queries/#inline-fragments"},"inline fragment"),":"),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n me {\n userName\n roles\n ... on User {\n email\n }\n }\n}\n")),(0,l.yg)("h3",{id:"logout-using-the-logout-mutation"},'Logout using the "logout" mutation'),(0,l.yg)("p",null,'Use the "logout" mutation to log a user out'),(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-graphql"},"mutation logout {\n logout\n}\n")),(0,l.yg)("h2",{id:"injecting-the-request"},"Injecting the Request"),(0,l.yg)("p",null,"You can inject the Symfony Request object in any query/mutation/field."),(0,l.yg)("p",null,"Most of the time, getting the request object is irrelevant. Indeed, it is GraphQLite's job to parse this request and\nmanage it for you. Sometimes yet, fetching the request can be needed. In those cases, simply type-hint on the request\nin any parameter of your query/mutation/field."),(0,l.yg)(r.A,{defaultValue:"php8",values:[{label:"PHP 8",value:"php8"},{label:"PHP 7",value:"php7"}],mdxType:"Tabs"},(0,l.yg)(u.A,{value:"php8",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n#[Query]\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n"))),(0,l.yg)(u.A,{value:"php7",mdxType:"TabItem"},(0,l.yg)("pre",null,(0,l.yg)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\Request;\n\n/**\n * @Query\n */\npublic function getUser(int $id, Request $request): User\n{\n // The $request object contains the Symfony Request.\n}\n")))))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/ce95b17c.9e8c5589.js b/assets/js/ce95b17c.1c7cb95e.js similarity index 96% rename from assets/js/ce95b17c.9e8c5589.js rename to assets/js/ce95b17c.1c7cb95e.js index ac77b27195..ac2c50afad 100644 --- a/assets/js/ce95b17c.9e8c5589.js +++ b/assets/js/ce95b17c.1c7cb95e.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4e3],{90428:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var a=t(58168),r=(t(96540),t(15680));t(67443);const l={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},o=void 0,i={unversionedId:"query-plan",id:"query-plan",title:"Query plan",description:"The problem",source:"@site/docs/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/next/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/query-plan.mdx",tags:[],version:"current",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"docs",previous:{title:"Operation complexity",permalink:"/docs/next/operation-complexity"},next:{title:"Prefetching records",permalink:"/docs/next/prefetch-method"}},u={},s=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],p={toc:s},d="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4e3],{90428:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>o,default:()=>h,frontMatter:()=>l,metadata:()=>i,toc:()=>s});var a=t(58168),r=(t(96540),t(15680));t(67443);const l={id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},o=void 0,i={unversionedId:"query-plan",id:"query-plan",title:"Query plan",description:"The problem",source:"@site/docs/query-plan.mdx",sourceDirName:".",slug:"/query-plan",permalink:"/docs/next/query-plan",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/docs/query-plan.mdx",tags:[],version:"current",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"query-plan",title:"Query plan",sidebar_label:"Query plan"},sidebar:"docs",previous:{title:"Operation complexity",permalink:"/docs/next/operation-complexity"},next:{title:"Prefetching records",permalink:"/docs/next/prefetch-method"}},u={},s=[{value:"The problem",id:"the-problem",level:2},{value:"Fetching the query plan",id:"fetching-the-query-plan",level:2}],p={toc:s},d="wrapper";function h(e){let{components:n,...t}=e;return(0,r.yg)(d,(0,a.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,r.yg)("h2",{id:"the-problem"},"The problem"),(0,r.yg)("p",null,'GraphQL naive implementations often suffer from the "N+1" problem.'),(0,r.yg)("p",null,"Let's have a look at the following query:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-graphql"},"{\n products {\n name\n manufacturer {\n name\n }\n }\n}\n")),(0,r.yg)("p",null,"A naive implementation will do this:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"1 query to fetch the list of products"),(0,r.yg)("li",{parentName:"ul"},"1 query per product to fetch the manufacturer")),(0,r.yg)("p",null,'Assuming we have "N" products, we will make "N+1" queries.'),(0,r.yg)("p",null,'There are several ways to fix this problem. Assuming you are using a relational database, one solution is to try to look\nahead and perform only one query with a JOIN between "products" and "manufacturers".'),(0,r.yg)("p",null,'But how do I know if I should make the JOIN between "products" and "manufacturers" or not? I need to know ahead\nof time.'),(0,r.yg)("p",null,"With GraphQLite, you can answer this question by tapping into the ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," object."),(0,r.yg)("h2",{id:"fetching-the-query-plan"},"Fetching the query plan"),(0,r.yg)("small",null,"Available in GraphQLite 4.0+"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-php"},"use GraphQL\\Type\\Definition\\ResolveInfo;\n\nclass ProductsController\n{\n /**\n * @return Product[]\n */\n #[Query]\n public function products(ResolveInfo $info): array\n {\n if (isset($info->getFieldSelection()['manufacturer']) {\n // Let's perform a request with a JOIN on manufacturer\n } else {\n // Let's perform a request without a JOIN on manufacturer\n }\n // ...\n }\n}\n")),(0,r.yg)("p",null,(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo")," is a class provided by Webonyx/GraphQL-PHP (the low-level GraphQL library used by GraphQLite).\nIt contains info about the query and what fields are requested. Using ",(0,r.yg)("inlineCode",{parentName:"p"},"ResolveInfo::getFieldSelection"),' you can analyze the query\nand decide whether you should perform additional "JOINS" in your query or not.'),(0,r.yg)("div",{class:"alert alert--info"},"As of the writing of this documentation, the ",(0,r.yg)("code",null,"ResolveInfo")," class is useful but somewhat limited. The ",(0,r.yg)("a",{href:"https://github.com/webonyx/graphql-php/pull/436"},'next version of Webonyx/GraphQL-PHP will add a "query plan"'),"that allows a deeper analysis of the query."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/cf877cff.f8eace7d.js b/assets/js/cf877cff.13543e94.js similarity index 84% rename from assets/js/cf877cff.f8eace7d.js rename to assets/js/cf877cff.13543e94.js index 7722cafdac..c340d5ae47 100644 --- a/assets/js/cf877cff.f8eace7d.js +++ b/assets/js/cf877cff.13543e94.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8177],{74259:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>c,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},o=void 0,s={unversionedId:"getting-started",id:"version-7.0.0/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-7.0.0/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/getting-started.md",tags:[],version:"7.0.0",lastUpdatedBy:"dependabot[bot]",lastUpdatedAt:1718658906,formattedLastUpdatedAt:"Jun 17, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},sidebar:"docs",previous:{title:"GraphQLite",permalink:"/docs/"},next:{title:"Symfony bundle",permalink:"/docs/symfony-bundle"}},d={},l=[],p={toc:l},g="wrapper";function c(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,r.A)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/universal-service-providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/other-frameworks"},"Get started with another framework (or no framework)"))))}c.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[8177],{74259:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>p,frontMatter:()=>n,metadata:()=>s,toc:()=>l});var r=a(58168),i=(a(96540),a(15680));a(67443);const n={id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},o=void 0,s={unversionedId:"getting-started",id:"version-7.0.0/getting-started",title:"Getting started",description:"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to",source:"@site/versioned_docs/version-7.0.0/getting-started.md",sourceDirName:".",slug:"/getting-started",permalink:"/docs/getting-started",draft:!1,editUrl:"https://github.com/thecodingmachine/graphqlite/edit/master/website/versioned_docs/version-7.0.0/getting-started.md",tags:[],version:"7.0.0",lastUpdatedBy:"Daniel Melchior",lastUpdatedAt:1721167897,formattedLastUpdatedAt:"Jul 16, 2024",frontMatter:{id:"getting-started",title:"Getting started",sidebar_label:"Getting Started"},sidebar:"docs",previous:{title:"GraphQLite",permalink:"/docs/"},next:{title:"Symfony bundle",permalink:"/docs/symfony-bundle"}},d={},l=[],c={toc:l},g="wrapper";function p(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,r.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("p",null,"GraphQLite is a framework agnostic library. You can use it in any PHP project as long as you know how to\ninject services in your favorite framework's container."),(0,i.yg)("p",null,"Currently, we provide bundle/packages to help you get started with Symfony, Laravel and any framework compatible\nwith container-interop/service-provider."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/symfony-bundle"},"Get started with Symfony")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/laravel-package"},"Get started with Laravel")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/universal-service-providers"},"Get started with a framework compatible with container-interop/service-provider")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("a",{parentName:"li",href:"/docs/other-frameworks"},"Get started with another framework (or no framework)"))))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/common.5c1c7fb8.js b/assets/js/common.fbe21386.js similarity index 52% rename from assets/js/common.5c1c7fb8.js rename to assets/js/common.fbe21386.js index 754b082a47..cd616e2e75 100644 --- a/assets/js/common.5c1c7fb8.js +++ b/assets/js/common.fbe21386.js @@ -1 +1 @@ -"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[2076],{15680:(t,e,n)=>{n.d(e,{xA:()=>l,yg:()=>p});var r=n(96540);function i(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function a(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,r)}return n}function o(t){for(var e=1;e=0||(i[n]=t[n]);return i}(t,e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(i[n]=t[n])}return i}var c=r.createContext({}),u=function(t){var e=r.useContext(c),n=e;return t&&(n="function"==typeof t?t(e):o(o({},e),t)),n},l=function(t){var e=u(t.components);return r.createElement(c.Provider,{value:e},t.children)},h="mdxType",f={inlineCode:"code",wrapper:function(t){var e=t.children;return r.createElement(r.Fragment,{},e)}},d=r.forwardRef((function(t,e){var n=t.components,i=t.mdxType,a=t.originalType,c=t.parentName,l=s(t,["components","mdxType","originalType","parentName"]),h=u(n),d=i,p=h["".concat(c,".").concat(d)]||h[d]||f[d]||a;return n?r.createElement(p,o(o({ref:e},l),{},{components:n})):r.createElement(p,o({ref:e},l))}));function p(t,e){var n=arguments,i=e&&e.mdxType;if("string"==typeof t||i){var a=n.length,o=new Array(a);o[0]=d;var s={};for(var c in e)hasOwnProperty.call(e,c)&&(s[c]=e[c]);s.originalType=t,s[h]="string"==typeof t?t:i,o[1]=s;for(var u=2;u{n.d(e,{K:()=>u});var r=n(96540),i={1362:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,6],n=[1,7],r=[1,8],i=[1,9],a=[1,12],o=[1,11],s=[1,15,24],c=[1,19],u=[1,31],l=[1,34],h=[1,32],f=[1,33],d=[1,35],p=[1,36],y=[1,37],g=[1,38],m=[1,41],v=[1,42],b=[1,43],_=[1,44],x=[15,24],w=[1,56],k=[1,57],T=[1,58],E=[1,59],C=[1,60],S=[1,61],A=[15,24,31,38,39,47,50,51,52,53,54,55,60,62],M=[15,24,29,31,38,39,43,47,50,51,52,53,54,55,60,62,77,78,79,80],N=[7,8,9,10,15,18,22,24],D=[47,77,78,79,80],O=[47,54,55,77,78,79,80],B=[47,50,51,52,53,77,78,79,80],L=[15,24,31],I=[1,93],R={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,direction:5,directive:6,direction_tb:7,direction_bt:8,direction_rl:9,direction_lr:10,graphConfig:11,openDirective:12,typeDirective:13,closeDirective:14,NEWLINE:15,":":16,argDirective:17,open_directive:18,type_directive:19,arg_directive:20,close_directive:21,CLASS_DIAGRAM:22,statements:23,EOF:24,statement:25,className:26,alphaNumToken:27,classLiteralName:28,GENERICTYPE:29,relationStatement:30,LABEL:31,classStatement:32,methodStatement:33,annotationStatement:34,clickStatement:35,cssClassStatement:36,CLASS:37,STYLE_SEPARATOR:38,STRUCT_START:39,members:40,STRUCT_STOP:41,ANNOTATION_START:42,ANNOTATION_END:43,MEMBER:44,SEPARATOR:45,relation:46,STR:47,relationType:48,lineType:49,AGGREGATION:50,EXTENSION:51,COMPOSITION:52,DEPENDENCY:53,LINE:54,DOTTED_LINE:55,CALLBACK:56,LINK:57,LINK_TARGET:58,CLICK:59,CALLBACK_NAME:60,CALLBACK_ARGS:61,HREF:62,CSSCLASS:63,commentToken:64,textToken:65,graphCodeTokens:66,textNoTagsToken:67,TAGSTART:68,TAGEND:69,"==":70,"--":71,PCT:72,DEFAULT:73,SPACE:74,MINUS:75,keywords:76,UNICODE_TEXT:77,NUM:78,ALPHA:79,BQUOTE_STR:80,$accept:0,$end:1},terminals_:{2:"error",7:"direction_tb",8:"direction_bt",9:"direction_rl",10:"direction_lr",15:"NEWLINE",16:":",18:"open_directive",19:"type_directive",20:"arg_directive",21:"close_directive",22:"CLASS_DIAGRAM",24:"EOF",29:"GENERICTYPE",31:"LABEL",37:"CLASS",38:"STYLE_SEPARATOR",39:"STRUCT_START",41:"STRUCT_STOP",42:"ANNOTATION_START",43:"ANNOTATION_END",44:"MEMBER",45:"SEPARATOR",47:"STR",50:"AGGREGATION",51:"EXTENSION",52:"COMPOSITION",53:"DEPENDENCY",54:"LINE",55:"DOTTED_LINE",56:"CALLBACK",57:"LINK",58:"LINK_TARGET",59:"CLICK",60:"CALLBACK_NAME",61:"CALLBACK_ARGS",62:"HREF",63:"CSSCLASS",66:"graphCodeTokens",68:"TAGSTART",69:"TAGEND",70:"==",71:"--",72:"PCT",73:"DEFAULT",74:"SPACE",75:"MINUS",76:"keywords",77:"UNICODE_TEXT",78:"NUM",79:"ALPHA",80:"BQUOTE_STR"},productions_:[0,[3,1],[3,1],[3,2],[5,1],[5,1],[5,1],[5,1],[4,1],[6,4],[6,6],[12,1],[13,1],[17,1],[14,1],[11,4],[23,1],[23,2],[23,3],[26,1],[26,1],[26,2],[26,2],[26,2],[25,1],[25,2],[25,1],[25,1],[25,1],[25,1],[25,1],[25,1],[25,1],[32,2],[32,4],[32,5],[32,7],[34,4],[40,1],[40,2],[33,1],[33,2],[33,1],[33,1],[30,3],[30,4],[30,4],[30,5],[46,3],[46,2],[46,2],[46,1],[48,1],[48,1],[48,1],[48,1],[49,1],[49,1],[35,3],[35,4],[35,3],[35,4],[35,4],[35,5],[35,3],[35,4],[35,4],[35,5],[35,3],[35,4],[35,4],[35,5],[36,3],[64,1],[64,1],[65,1],[65,1],[65,1],[65,1],[65,1],[65,1],[65,1],[67,1],[67,1],[67,1],[67,1],[27,1],[27,1],[27,1],[28,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:r.setDirection("TB");break;case 5:r.setDirection("BT");break;case 6:r.setDirection("RL");break;case 7:r.setDirection("LR");break;case 11:r.parseDirective("%%{","open_directive");break;case 12:r.parseDirective(a[s],"type_directive");break;case 13:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 14:r.parseDirective("}%%","close_directive","class");break;case 19:case 20:this.$=a[s];break;case 21:this.$=a[s-1]+a[s];break;case 22:case 23:this.$=a[s-1]+"~"+a[s];break;case 24:r.addRelation(a[s]);break;case 25:a[s-1].title=r.cleanupLabel(a[s]),r.addRelation(a[s-1]);break;case 33:r.addClass(a[s]);break;case 34:r.addClass(a[s-2]),r.setCssClass(a[s-2],a[s]);break;case 35:r.addClass(a[s-3]),r.addMembers(a[s-3],a[s-1]);break;case 36:r.addClass(a[s-5]),r.setCssClass(a[s-5],a[s-3]),r.addMembers(a[s-5],a[s-1]);break;case 37:r.addAnnotation(a[s],a[s-2]);break;case 38:this.$=[a[s]];break;case 39:a[s].push(a[s-1]),this.$=a[s];break;case 40:case 42:case 43:break;case 41:r.addMember(a[s-1],r.cleanupLabel(a[s]));break;case 44:this.$={id1:a[s-2],id2:a[s],relation:a[s-1],relationTitle1:"none",relationTitle2:"none"};break;case 45:this.$={id1:a[s-3],id2:a[s],relation:a[s-1],relationTitle1:a[s-2],relationTitle2:"none"};break;case 46:this.$={id1:a[s-3],id2:a[s],relation:a[s-2],relationTitle1:"none",relationTitle2:a[s-1]};break;case 47:this.$={id1:a[s-4],id2:a[s],relation:a[s-2],relationTitle1:a[s-3],relationTitle2:a[s-1]};break;case 48:this.$={type1:a[s-2],type2:a[s],lineType:a[s-1]};break;case 49:this.$={type1:"none",type2:a[s],lineType:a[s-1]};break;case 50:this.$={type1:a[s-1],type2:"none",lineType:a[s]};break;case 51:this.$={type1:"none",type2:"none",lineType:a[s]};break;case 52:this.$=r.relationType.AGGREGATION;break;case 53:this.$=r.relationType.EXTENSION;break;case 54:this.$=r.relationType.COMPOSITION;break;case 55:this.$=r.relationType.DEPENDENCY;break;case 56:this.$=r.lineType.LINE;break;case 57:this.$=r.lineType.DOTTED_LINE;break;case 58:case 64:this.$=a[s-2],r.setClickEvent(a[s-1],a[s]);break;case 59:case 65:this.$=a[s-3],r.setClickEvent(a[s-2],a[s-1]),r.setTooltip(a[s-2],a[s]);break;case 60:case 68:this.$=a[s-2],r.setLink(a[s-1],a[s]);break;case 61:case 69:this.$=a[s-3],r.setLink(a[s-2],a[s-1],a[s]);break;case 62:case 70:this.$=a[s-3],r.setLink(a[s-2],a[s-1]),r.setTooltip(a[s-2],a[s]);break;case 63:case 71:this.$=a[s-4],r.setLink(a[s-3],a[s-2],a[s]),r.setTooltip(a[s-3],a[s-1]);break;case 66:this.$=a[s-3],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 67:this.$=a[s-4],r.setClickEvent(a[s-3],a[s-2],a[s-1]),r.setTooltip(a[s-3],a[s]);break;case 72:r.setCssClass(a[s-1],a[s])}},table:[{3:1,4:2,5:3,6:4,7:e,8:n,9:r,10:i,11:5,12:10,18:a,22:o},{1:[3]},{1:[2,1]},{1:[2,2]},{3:13,4:2,5:3,6:4,7:e,8:n,9:r,10:i,11:5,12:10,18:a,22:o},{1:[2,8]},t(s,[2,4]),t(s,[2,5]),t(s,[2,6]),t(s,[2,7]),{13:14,19:[1,15]},{15:[1,16]},{19:[2,11]},{1:[2,3]},{14:17,16:[1,18],21:c},t([16,21],[2,12]),{5:29,6:28,7:e,8:n,9:r,10:i,12:10,18:a,23:20,25:21,26:30,27:39,28:40,30:22,32:23,33:24,34:25,35:26,36:27,37:u,42:l,44:h,45:f,56:d,57:p,59:y,63:g,77:m,78:v,79:b,80:_},{15:[1,45]},{17:46,20:[1,47]},{15:[2,14]},{24:[1,48]},{15:[1,49],24:[2,16]},t(x,[2,24],{31:[1,50]}),t(x,[2,26]),t(x,[2,27]),t(x,[2,28]),t(x,[2,29]),t(x,[2,30]),t(x,[2,31]),t(x,[2,32]),t(x,[2,40],{46:51,48:54,49:55,31:[1,53],47:[1,52],50:w,51:k,52:T,53:E,54:C,55:S}),{26:62,27:39,28:40,77:m,78:v,79:b,80:_},t(x,[2,42]),t(x,[2,43]),{27:63,77:m,78:v,79:b},{26:64,27:39,28:40,77:m,78:v,79:b,80:_},{26:65,27:39,28:40,77:m,78:v,79:b,80:_},{26:66,27:39,28:40,77:m,78:v,79:b,80:_},{47:[1,67]},t(A,[2,19],{27:39,28:40,26:68,29:[1,69],77:m,78:v,79:b,80:_}),t(A,[2,20],{29:[1,70]}),t(M,[2,86]),t(M,[2,87]),t(M,[2,88]),t([15,24,29,31,38,39,47,50,51,52,53,54,55,60,62],[2,89]),t(N,[2,9]),{14:71,21:c},{21:[2,13]},{1:[2,15]},{5:29,6:28,7:e,8:n,9:r,10:i,12:10,18:a,23:72,24:[2,17],25:21,26:30,27:39,28:40,30:22,32:23,33:24,34:25,35:26,36:27,37:u,42:l,44:h,45:f,56:d,57:p,59:y,63:g,77:m,78:v,79:b,80:_},t(x,[2,25]),{26:73,27:39,28:40,47:[1,74],77:m,78:v,79:b,80:_},{46:75,48:54,49:55,50:w,51:k,52:T,53:E,54:C,55:S},t(x,[2,41]),{49:76,54:C,55:S},t(D,[2,51],{48:77,50:w,51:k,52:T,53:E}),t(O,[2,52]),t(O,[2,53]),t(O,[2,54]),t(O,[2,55]),t(B,[2,56]),t(B,[2,57]),t(x,[2,33],{38:[1,78],39:[1,79]}),{43:[1,80]},{47:[1,81]},{47:[1,82]},{60:[1,83],62:[1,84]},{27:85,77:m,78:v,79:b},t(A,[2,21]),t(A,[2,22]),t(A,[2,23]),{15:[1,86]},{24:[2,18]},t(L,[2,44]),{26:87,27:39,28:40,77:m,78:v,79:b,80:_},{26:88,27:39,28:40,47:[1,89],77:m,78:v,79:b,80:_},t(D,[2,50],{48:90,50:w,51:k,52:T,53:E}),t(D,[2,49]),{27:91,77:m,78:v,79:b},{40:92,44:I},{26:94,27:39,28:40,77:m,78:v,79:b,80:_},t(x,[2,58],{47:[1,95]}),t(x,[2,60],{47:[1,97],58:[1,96]}),t(x,[2,64],{47:[1,98],61:[1,99]}),t(x,[2,68],{47:[1,101],58:[1,100]}),t(x,[2,72]),t(N,[2,10]),t(L,[2,46]),t(L,[2,45]),{26:102,27:39,28:40,77:m,78:v,79:b,80:_},t(D,[2,48]),t(x,[2,34],{39:[1,103]}),{41:[1,104]},{40:105,41:[2,38],44:I},t(x,[2,37]),t(x,[2,59]),t(x,[2,61]),t(x,[2,62],{58:[1,106]}),t(x,[2,65]),t(x,[2,66],{47:[1,107]}),t(x,[2,69]),t(x,[2,70],{58:[1,108]}),t(L,[2,47]),{40:109,44:I},t(x,[2,35]),{41:[2,39]},t(x,[2,63]),t(x,[2,67]),t(x,[2,71]),{41:[1,110]},t(x,[2,36])],defaultActions:{2:[2,1],3:[2,2],5:[2,8],12:[2,11],13:[2,3],19:[2,14],47:[2,13],48:[2,15],72:[2,18],105:[2,39]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=a.slice.call(arguments,1),f=Object.create(this.lexer),d={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(d.yy[p]=this.yy[p]);f.setInput(t,d.yy),d.yy.lexer=f,d.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var y=f.yylloc;a.push(y);var g=f.options&&f.options.ranges;function m(){var t;return"number"!=typeof(t=r.pop()||f.lex()||1)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof d.yy.parseError?this.parseError=d.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var v,b,_,x,w,k,T,E,C,S={};;){if(_=n[n.length-1],this.defaultActions[_]?x=this.defaultActions[_]:(null==v&&(v=m()),x=o[_]&&o[_][v]),void 0===x||!x.length||!x[0]){var A;for(k in C=[],o[_])this.terminals_[k]&&k>2&&C.push("'"+this.terminals_[k]+"'");A=f.showPosition?"Parse error on line "+(c+1)+":\n"+f.showPosition()+"\nExpecting "+C.join(", ")+", got '"+(this.terminals_[v]||v)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==v?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(A,{text:f.match,token:this.terminals_[v]||v,line:f.yylineno,loc:y,expected:C})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+v);switch(x[0]){case 1:n.push(v),i.push(f.yytext),a.push(f.yylloc),n.push(x[1]),v=null,b?(v=b,b=null):(u=f.yyleng,s=f.yytext,c=f.yylineno,y=f.yylloc,l>0&&l--);break;case 2:if(T=this.productions_[x[1]][1],S.$=i[i.length-T],S._$={first_line:a[a.length-(T||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(T||1)].first_column,last_column:a[a.length-1].last_column},g&&(S._$.range=[a[a.length-(T||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[s,u,c,d.yy,x[1],i,a].concat(h))))return w;T&&(n=n.slice(0,-1*T*2),i=i.slice(0,-1*T),a=a.slice(0,-1*T)),n.push(this.productions_[x[1]][0]),i.push(S.$),a.push(S._$),E=o[n[n.length-2]][n[n.length-1]],n.push(E);break;case 3:return!0}}return!0}},F={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),18;case 1:return 7;case 2:return 8;case 3:return 9;case 4:return 10;case 5:return this.begin("type_directive"),19;case 6:return this.popState(),this.begin("arg_directive"),16;case 7:return this.popState(),this.popState(),21;case 8:return 20;case 9:case 10:case 12:case 19:break;case 11:return 15;case 13:case 14:return 22;case 15:return this.begin("struct"),39;case 16:return"EOF_IN_STRUCT";case 17:return"OPEN_IN_STRUCT";case 18:return this.popState(),41;case 20:return"MEMBER";case 21:return 37;case 22:return 63;case 23:return 56;case 24:return 57;case 25:return 59;case 26:return 42;case 27:return 43;case 28:this.begin("generic");break;case 29:case 32:case 35:case 38:case 41:case 44:this.popState();break;case 30:return"GENERICTYPE";case 31:this.begin("string");break;case 33:return"STR";case 34:this.begin("bqstring");break;case 36:return"BQUOTE_STR";case 37:this.begin("href");break;case 39:return 62;case 40:this.begin("callback_name");break;case 42:this.popState(),this.begin("callback_args");break;case 43:return 60;case 45:return 61;case 46:case 47:case 48:case 49:return 58;case 50:case 51:return 51;case 52:case 53:return 53;case 54:return 52;case 55:return 50;case 56:return 54;case 57:return 55;case 58:return 31;case 59:return 38;case 60:return 75;case 61:return"DOT";case 62:return"PLUS";case 63:return 72;case 64:case 65:return"EQUALS";case 66:return 79;case 67:return"PUNCTUATION";case 68:return 78;case 69:return 77;case 70:return 74;case 71:return 24}},rules:[/^(?:%%\{)/,/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:classDiagram-v2\b)/,/^(?:classDiagram\b)/,/^(?:[{])/,/^(?:$)/,/^(?:[{])/,/^(?:[}])/,/^(?:[\n])/,/^(?:[^{}\n]*)/,/^(?:class\b)/,/^(?:cssClass\b)/,/^(?:callback\b)/,/^(?:link\b)/,/^(?:click\b)/,/^(?:<<)/,/^(?:>>)/,/^(?:[~])/,/^(?:[~])/,/^(?:[^~]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:[`])/,/^(?:[`])/,/^(?:[^`]+)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:\s*<\|)/,/^(?:\s*\|>)/,/^(?:\s*>)/,/^(?:\s*<)/,/^(?:\s*\*)/,/^(?:\s*o\b)/,/^(?:--)/,/^(?:\.\.)/,/^(?::{1}[^:\n;]+)/,/^(?::{3})/,/^(?:-)/,/^(?:\.)/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:\w+)/,/^(?:[!"#$%&'*+,-.`?\\/])/,/^(?:[0-9]+)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\s)/,/^(?:$)/],conditions:{arg_directive:{rules:[7,8],inclusive:!1},type_directive:{rules:[6,7],inclusive:!1},open_directive:{rules:[5],inclusive:!1},callback_args:{rules:[44,45],inclusive:!1},callback_name:{rules:[41,42,43],inclusive:!1},href:{rules:[38,39],inclusive:!1},struct:{rules:[16,17,18,19,20],inclusive:!1},generic:{rules:[29,30],inclusive:!1},bqstring:{rules:[35,36],inclusive:!1},string:{rules:[32,33],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,9,10,11,12,13,14,15,21,22,23,24,25,26,27,28,31,34,37,40,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71],inclusive:!0}}};function P(){this.yy={}}return R.lexer=F,P.prototype=R,R.Parser=P,new P}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(8218).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},5890:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,23,41],i=[1,17],a=[1,20],o=[1,25],s=[1,26],c=[1,27],u=[1,28],l=[1,37],h=[23,38,39],f=[4,6,9,11,23,41],d=[34,35,36,37],p=[22,29],y=[1,55],g={trace:function(){},yy:{},symbols_:{error:2,start:3,ER_DIAGRAM:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,entityName:17,relSpec:18,role:19,BLOCK_START:20,attributes:21,BLOCK_STOP:22,ALPHANUM:23,attribute:24,attributeType:25,attributeName:26,attributeKeyType:27,attributeComment:28,ATTRIBUTE_WORD:29,ATTRIBUTE_KEY:30,COMMENT:31,cardinality:32,relType:33,ZERO_OR_ONE:34,ZERO_OR_MORE:35,ONE_OR_MORE:36,ONLY_ONE:37,NON_IDENTIFYING:38,IDENTIFYING:39,WORD:40,open_directive:41,type_directive:42,arg_directive:43,close_directive:44,$accept:0,$end:1},terminals_:{2:"error",4:"ER_DIAGRAM",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",20:"BLOCK_START",22:"BLOCK_STOP",23:"ALPHANUM",29:"ATTRIBUTE_WORD",30:"ATTRIBUTE_KEY",31:"COMMENT",34:"ZERO_OR_ONE",35:"ZERO_OR_MORE",36:"ONE_OR_MORE",37:"ONLY_ONE",38:"NON_IDENTIFYING",39:"IDENTIFYING",40:"WORD",41:"open_directive",42:"type_directive",43:"arg_directive",44:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,5],[10,4],[10,3],[10,1],[17,1],[21,1],[21,2],[24,2],[24,3],[24,3],[24,4],[25,1],[26,1],[27,1],[28,1],[18,3],[32,1],[32,1],[32,1],[32,1],[33,1],[33,1],[19,1],[19,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:break;case 3:case 7:case 8:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:case 16:case 23:case 24:case 25:case 35:this.$=a[s];break;case 12:r.addEntity(a[s-4]),r.addEntity(a[s-2]),r.addRelationship(a[s-4],a[s],a[s-2],a[s-3]);break;case 13:r.addEntity(a[s-3]),r.addAttributes(a[s-3],a[s-1]);break;case 14:r.addEntity(a[s-2]);break;case 15:r.addEntity(a[s]);break;case 17:this.$=[a[s]];break;case 18:a[s].push(a[s-1]),this.$=a[s];break;case 19:this.$={attributeType:a[s-1],attributeName:a[s]};break;case 20:this.$={attributeType:a[s-2],attributeName:a[s-1],attributeKeyType:a[s]};break;case 21:this.$={attributeType:a[s-2],attributeName:a[s-1],attributeComment:a[s]};break;case 22:this.$={attributeType:a[s-3],attributeName:a[s-2],attributeKeyType:a[s-1],attributeComment:a[s]};break;case 26:case 34:this.$=a[s].replace(/"/g,"");break;case 27:this.$={cardA:a[s],relType:a[s-1],cardB:a[s-2]};break;case 28:this.$=r.Cardinality.ZERO_OR_ONE;break;case 29:this.$=r.Cardinality.ZERO_OR_MORE;break;case 30:this.$=r.Cardinality.ONE_OR_MORE;break;case 31:this.$=r.Cardinality.ONLY_ONE;break;case 32:this.$=r.Identification.NON_IDENTIFYING;break;case 33:this.$=r.Identification.IDENTIFYING;break;case 36:r.parseDirective("%%{","open_directive");break;case 37:r.parseDirective(a[s],"type_directive");break;case 38:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 39:r.parseDirective("}%%","close_directive","er")}},table:[{3:1,4:e,7:3,12:4,41:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,41:n},{13:8,42:[1,9]},{42:[2,36]},{6:[1,10],7:15,8:11,9:[1,12],10:13,11:[1,14],12:4,17:16,23:i,41:n},{1:[2,2]},{14:18,15:[1,19],44:a},t([15,44],[2,37]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:15,10:21,12:4,17:16,23:i,41:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),t(r,[2,15],{18:22,32:24,20:[1,23],34:o,35:s,36:c,37:u}),t([6,9,11,15,20,23,34,35,36,37,41],[2,16]),{11:[1,29]},{16:30,43:[1,31]},{11:[2,39]},t(r,[2,5]),{17:32,23:i},{21:33,22:[1,34],24:35,25:36,29:l},{33:38,38:[1,39],39:[1,40]},t(h,[2,28]),t(h,[2,29]),t(h,[2,30]),t(h,[2,31]),t(f,[2,9]),{14:41,44:a},{44:[2,38]},{15:[1,42]},{22:[1,43]},t(r,[2,14]),{21:44,22:[2,17],24:35,25:36,29:l},{26:45,29:[1,46]},{29:[2,23]},{32:47,34:o,35:s,36:c,37:u},t(d,[2,32]),t(d,[2,33]),{11:[1,48]},{19:49,23:[1,51],40:[1,50]},t(r,[2,13]),{22:[2,18]},t(p,[2,19],{27:52,28:53,30:[1,54],31:y}),t([22,29,30,31],[2,24]),{23:[2,27]},t(f,[2,10]),t(r,[2,12]),t(r,[2,34]),t(r,[2,35]),t(p,[2,20],{28:56,31:y}),t(p,[2,21]),t([22,29,31],[2,25]),t(p,[2,26]),t(p,[2,22])],defaultActions:{5:[2,36],7:[2,2],20:[2,39],31:[2,38],37:[2,23],44:[2,18],47:[2,27]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=a.slice.call(arguments,1),f=Object.create(this.lexer),d={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(d.yy[p]=this.yy[p]);f.setInput(t,d.yy),d.yy.lexer=f,d.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var y=f.yylloc;a.push(y);var g=f.options&&f.options.ranges;function m(){var t;return"number"!=typeof(t=r.pop()||f.lex()||1)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof d.yy.parseError?this.parseError=d.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var v,b,_,x,w,k,T,E,C,S={};;){if(_=n[n.length-1],this.defaultActions[_]?x=this.defaultActions[_]:(null==v&&(v=m()),x=o[_]&&o[_][v]),void 0===x||!x.length||!x[0]){var A;for(k in C=[],o[_])this.terminals_[k]&&k>2&&C.push("'"+this.terminals_[k]+"'");A=f.showPosition?"Parse error on line "+(c+1)+":\n"+f.showPosition()+"\nExpecting "+C.join(", ")+", got '"+(this.terminals_[v]||v)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==v?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(A,{text:f.match,token:this.terminals_[v]||v,line:f.yylineno,loc:y,expected:C})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+v);switch(x[0]){case 1:n.push(v),i.push(f.yytext),a.push(f.yylloc),n.push(x[1]),v=null,b?(v=b,b=null):(u=f.yyleng,s=f.yytext,c=f.yylineno,y=f.yylloc,l>0&&l--);break;case 2:if(T=this.productions_[x[1]][1],S.$=i[i.length-T],S._$={first_line:a[a.length-(T||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(T||1)].first_column,last_column:a[a.length-1].last_column},g&&(S._$.range=[a[a.length-(T||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[s,u,c,d.yy,x[1],i,a].concat(h))))return w;T&&(n=n.slice(0,-1*T*2),i=i.slice(0,-1*T),a=a.slice(0,-1*T)),n.push(this.productions_[x[1]][0]),i.push(S.$),a.push(S._$),E=o[n[n.length-2]][n[n.length-1]],n.push(E);break;case 3:return!0}}return!0}},m={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),41;case 1:return this.begin("type_directive"),42;case 2:return this.popState(),this.begin("arg_directive"),15;case 3:return this.popState(),this.popState(),44;case 4:return 43;case 5:case 6:case 8:case 13:case 17:break;case 7:return 11;case 9:return 9;case 10:return 40;case 11:return 4;case 12:return this.begin("block"),20;case 14:return 30;case 15:return 29;case 16:return 31;case 18:return this.popState(),22;case 19:case 32:return e.yytext[0];case 20:case 24:return 34;case 21:case 25:return 35;case 22:case 26:return 36;case 23:return 37;case 27:case 29:case 30:return 38;case 28:return 39;case 31:return 23;case 33:return 6}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:[\s]+)/i,/^(?:"[^"]*")/i,/^(?:erDiagram\b)/i,/^(?:\{)/i,/^(?:\s+)/i,/^(?:(?:PK)|(?:FK))/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:"[^"]*")/i,/^(?:[\n]+)/i,/^(?:\})/i,/^(?:.)/i,/^(?:\|o\b)/i,/^(?:\}o\b)/i,/^(?:\}\|)/i,/^(?:\|\|)/i,/^(?:o\|)/i,/^(?:o\{)/i,/^(?:\|\{)/i,/^(?:\.\.)/i,/^(?:--)/i,/^(?:\.-)/i,/^(?:-\.)/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:.)/i,/^(?:$)/i],conditions:{open_directive:{rules:[1],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},block:{rules:[13,14,15,16,17,18,19],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,20,21,22,23,24,25,26,27,28,29,30,31,32,33],inclusive:!0}}};function v(){this.yy={}}return g.lexer=m,v.prototype=g,g.Parser=v,new v}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(8009).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},3602:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,9],n=[1,7],r=[1,6],i=[1,8],a=[1,20,21,22,23,38,47,59,60,79,80,81,82,83,84,88,98,99,102,104,105,111,112,113,114,115,116,117,118,119,120],o=[2,10],s=[1,20],c=[1,21],u=[1,22],l=[1,23],h=[1,30],f=[1,59],d=[1,45],p=[1,49],y=[1,33],g=[1,34],m=[1,35],v=[1,36],b=[1,37],_=[1,53],x=[1,60],w=[1,48],k=[1,50],T=[1,52],E=[1,56],C=[1,57],S=[1,38],A=[1,39],M=[1,40],N=[1,41],D=[1,58],O=[1,47],B=[1,51],L=[1,54],I=[1,55],R=[1,46],F=[1,63],P=[1,68],j=[1,20,21,22,23,38,42,47,59,60,79,80,81,82,83,84,88,98,99,102,104,105,111,112,113,114,115,116,117,118,119,120],Y=[1,72],z=[1,71],U=[1,73],q=[20,21,23,74,75],H=[1,94],$=[1,99],W=[1,102],V=[1,103],G=[1,96],X=[1,101],Z=[1,104],K=[1,97],Q=[1,109],J=[1,108],tt=[1,98],et=[1,100],nt=[1,105],rt=[1,106],it=[1,107],at=[1,110],ot=[20,21,22,23,74,75],st=[20,21,22,23,48,74,75],ct=[20,21,22,23,40,47,48,50,52,54,56,58,59,60,62,64,66,67,69,74,75,84,88,98,99,102,104,105,115,116,117,118,119,120],ut=[20,21,23],lt=[20,21,23,47,59,60,74,75,84,88,98,99,102,104,105,115,116,117,118,119,120],ht=[1,12,20,21,22,23,24,38,42,47,59,60,79,80,81,82,83,84,88,98,99,102,104,105,111,112,113,114,115,116,117,118,119,120],ft=[47,59,60,84,88,98,99,102,104,105,115,116,117,118,119,120],dt=[1,143],pt=[1,151],yt=[1,152],gt=[1,153],mt=[1,154],vt=[1,138],bt=[1,139],_t=[1,135],xt=[1,146],wt=[1,147],kt=[1,148],Tt=[1,149],Et=[1,150],Ct=[1,155],St=[1,156],At=[1,141],Mt=[1,144],Nt=[1,140],Dt=[1,137],Ot=[20,21,22,23,38,42,47,59,60,79,80,81,82,83,84,88,98,99,102,104,105,111,112,113,114,115,116,117,118,119,120],Bt=[1,159],Lt=[20,21,22,23,26,47,59,60,84,98,99,102,104,105,115,116,117,118,119,120],It=[20,21,22,23,24,26,38,40,41,42,47,51,53,55,57,59,60,61,63,65,66,68,70,74,75,79,80,81,82,83,84,85,88,98,99,102,104,105,106,107,115,116,117,118,119,120],Rt=[12,21,22,24],Ft=[22,99],Pt=[1,242],jt=[1,237],Yt=[1,238],zt=[1,246],Ut=[1,243],qt=[1,240],Ht=[1,239],$t=[1,241],Wt=[1,244],Vt=[1,245],Gt=[1,247],Xt=[1,265],Zt=[20,21,23,99],Kt=[20,21,22,23,59,60,79,95,98,99,102,103,104,105,106],Qt={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,directive:5,openDirective:6,typeDirective:7,closeDirective:8,separator:9,":":10,argDirective:11,open_directive:12,type_directive:13,arg_directive:14,close_directive:15,graphConfig:16,document:17,line:18,statement:19,SEMI:20,NEWLINE:21,SPACE:22,EOF:23,GRAPH:24,NODIR:25,DIR:26,FirstStmtSeperator:27,ending:28,endToken:29,spaceList:30,spaceListNewline:31,verticeStatement:32,styleStatement:33,linkStyleStatement:34,classDefStatement:35,classStatement:36,clickStatement:37,subgraph:38,text:39,SQS:40,SQE:41,end:42,direction:43,link:44,node:45,vertex:46,AMP:47,STYLE_SEPARATOR:48,idString:49,PS:50,PE:51,"(-":52,"-)":53,STADIUMSTART:54,STADIUMEND:55,SUBROUTINESTART:56,SUBROUTINEEND:57,VERTEX_WITH_PROPS_START:58,ALPHA:59,COLON:60,PIPE:61,CYLINDERSTART:62,CYLINDEREND:63,DIAMOND_START:64,DIAMOND_STOP:65,TAGEND:66,TRAPSTART:67,TRAPEND:68,INVTRAPSTART:69,INVTRAPEND:70,linkStatement:71,arrowText:72,TESTSTR:73,START_LINK:74,LINK:75,textToken:76,STR:77,keywords:78,STYLE:79,LINKSTYLE:80,CLASSDEF:81,CLASS:82,CLICK:83,DOWN:84,UP:85,textNoTags:86,textNoTagsToken:87,DEFAULT:88,stylesOpt:89,alphaNum:90,CALLBACKNAME:91,CALLBACKARGS:92,HREF:93,LINK_TARGET:94,HEX:95,numList:96,INTERPOLATE:97,NUM:98,COMMA:99,style:100,styleComponent:101,MINUS:102,UNIT:103,BRKT:104,DOT:105,PCT:106,TAGSTART:107,alphaNumToken:108,idStringToken:109,alphaNumStatement:110,direction_tb:111,direction_bt:112,direction_rl:113,direction_lr:114,PUNCTUATION:115,UNICODE_TEXT:116,PLUS:117,EQUALS:118,MULT:119,UNDERSCORE:120,graphCodeTokens:121,ARROW_CROSS:122,ARROW_POINT:123,ARROW_CIRCLE:124,ARROW_OPEN:125,QUOTE:126,$accept:0,$end:1},terminals_:{2:"error",10:":",12:"open_directive",13:"type_directive",14:"arg_directive",15:"close_directive",20:"SEMI",21:"NEWLINE",22:"SPACE",23:"EOF",24:"GRAPH",25:"NODIR",26:"DIR",38:"subgraph",40:"SQS",41:"SQE",42:"end",47:"AMP",48:"STYLE_SEPARATOR",50:"PS",51:"PE",52:"(-",53:"-)",54:"STADIUMSTART",55:"STADIUMEND",56:"SUBROUTINESTART",57:"SUBROUTINEEND",58:"VERTEX_WITH_PROPS_START",59:"ALPHA",60:"COLON",61:"PIPE",62:"CYLINDERSTART",63:"CYLINDEREND",64:"DIAMOND_START",65:"DIAMOND_STOP",66:"TAGEND",67:"TRAPSTART",68:"TRAPEND",69:"INVTRAPSTART",70:"INVTRAPEND",73:"TESTSTR",74:"START_LINK",75:"LINK",77:"STR",79:"STYLE",80:"LINKSTYLE",81:"CLASSDEF",82:"CLASS",83:"CLICK",84:"DOWN",85:"UP",88:"DEFAULT",91:"CALLBACKNAME",92:"CALLBACKARGS",93:"HREF",94:"LINK_TARGET",95:"HEX",97:"INTERPOLATE",98:"NUM",99:"COMMA",102:"MINUS",103:"UNIT",104:"BRKT",105:"DOT",106:"PCT",107:"TAGSTART",111:"direction_tb",112:"direction_bt",113:"direction_rl",114:"direction_lr",115:"PUNCTUATION",116:"UNICODE_TEXT",117:"PLUS",118:"EQUALS",119:"MULT",120:"UNDERSCORE",122:"ARROW_CROSS",123:"ARROW_POINT",124:"ARROW_CIRCLE",125:"ARROW_OPEN",126:"QUOTE"},productions_:[0,[3,1],[3,2],[5,4],[5,6],[6,1],[7,1],[11,1],[8,1],[4,2],[17,0],[17,2],[18,1],[18,1],[18,1],[18,1],[18,1],[16,2],[16,2],[16,2],[16,3],[28,2],[28,1],[29,1],[29,1],[29,1],[27,1],[27,1],[27,2],[31,2],[31,2],[31,1],[31,1],[30,2],[30,1],[19,2],[19,2],[19,2],[19,2],[19,2],[19,2],[19,9],[19,6],[19,4],[19,1],[9,1],[9,1],[9,1],[32,3],[32,4],[32,2],[32,1],[45,1],[45,5],[45,3],[46,4],[46,6],[46,4],[46,4],[46,4],[46,8],[46,4],[46,4],[46,4],[46,6],[46,4],[46,4],[46,4],[46,4],[46,4],[46,1],[44,2],[44,3],[44,3],[44,1],[44,3],[71,1],[72,3],[39,1],[39,2],[39,1],[78,1],[78,1],[78,1],[78,1],[78,1],[78,1],[78,1],[78,1],[78,1],[78,1],[78,1],[86,1],[86,2],[35,5],[35,5],[36,5],[37,2],[37,4],[37,3],[37,5],[37,2],[37,4],[37,4],[37,6],[37,2],[37,4],[37,2],[37,4],[37,4],[37,6],[33,5],[33,5],[34,5],[34,5],[34,9],[34,9],[34,7],[34,7],[96,1],[96,3],[89,1],[89,3],[100,1],[100,2],[101,1],[101,1],[101,1],[101,1],[101,1],[101,1],[101,1],[101,1],[101,1],[101,1],[101,1],[76,1],[76,1],[76,1],[76,1],[76,1],[76,1],[87,1],[87,1],[87,1],[87,1],[49,1],[49,2],[90,1],[90,2],[110,1],[110,1],[110,1],[110,1],[43,1],[43,1],[43,1],[43,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[109,1],[109,1],[109,1],[109,1],[109,1],[109,1],[109,1],[109,1],[109,1],[109,1],[109,1],[109,1],[109,1],[109,1],[109,1],[109,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1],[121,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 5:r.parseDirective("%%{","open_directive");break;case 6:r.parseDirective(a[s],"type_directive");break;case 7:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 8:r.parseDirective("}%%","close_directive","flowchart");break;case 10:case 36:case 37:case 38:case 39:case 40:this.$=[];break;case 11:a[s]!==[]&&a[s-1].push(a[s]),this.$=a[s-1];break;case 12:case 78:case 80:case 92:case 148:case 150:case 151:case 74:case 146:this.$=a[s];break;case 19:r.setDirection("TB"),this.$="TB";break;case 20:r.setDirection(a[s-1]),this.$=a[s-1];break;case 35:this.$=a[s-1].nodes;break;case 41:this.$=r.addSubGraph(a[s-6],a[s-1],a[s-4]);break;case 42:this.$=r.addSubGraph(a[s-3],a[s-1],a[s-3]);break;case 43:this.$=r.addSubGraph(void 0,a[s-1],void 0);break;case 48:r.addLink(a[s-2].stmt,a[s],a[s-1]),this.$={stmt:a[s],nodes:a[s].concat(a[s-2].nodes)};break;case 49:r.addLink(a[s-3].stmt,a[s-1],a[s-2]),this.$={stmt:a[s-1],nodes:a[s-1].concat(a[s-3].nodes)};break;case 50:this.$={stmt:a[s-1],nodes:a[s-1]};break;case 51:this.$={stmt:a[s],nodes:a[s]};break;case 52:case 119:case 121:this.$=[a[s]];break;case 53:this.$=a[s-4].concat(a[s]);break;case 54:this.$=[a[s-2]],r.setClass(a[s-2],a[s]);break;case 55:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"square");break;case 56:this.$=a[s-5],r.addVertex(a[s-5],a[s-2],"circle");break;case 57:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"ellipse");break;case 58:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"stadium");break;case 59:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"subroutine");break;case 60:this.$=a[s-7],r.addVertex(a[s-7],a[s-1],"rect",void 0,void 0,void 0,Object.fromEntries([[a[s-5],a[s-3]]]));break;case 61:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"cylinder");break;case 62:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"round");break;case 63:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"diamond");break;case 64:this.$=a[s-5],r.addVertex(a[s-5],a[s-2],"hexagon");break;case 65:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"odd");break;case 66:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"trapezoid");break;case 67:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"inv_trapezoid");break;case 68:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"lean_right");break;case 69:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"lean_left");break;case 70:this.$=a[s],r.addVertex(a[s]);break;case 71:a[s-1].text=a[s],this.$=a[s-1];break;case 72:case 73:a[s-2].text=a[s-1],this.$=a[s-2];break;case 75:var c=r.destructLink(a[s],a[s-2]);this.$={type:c.type,stroke:c.stroke,length:c.length,text:a[s-1]};break;case 76:c=r.destructLink(a[s]),this.$={type:c.type,stroke:c.stroke,length:c.length};break;case 77:this.$=a[s-1];break;case 79:case 93:case 149:case 147:this.$=a[s-1]+""+a[s];break;case 94:case 95:this.$=a[s-4],r.addClass(a[s-2],a[s]);break;case 96:this.$=a[s-4],r.setClass(a[s-2],a[s]);break;case 97:case 105:this.$=a[s-1],r.setClickEvent(a[s-1],a[s]);break;case 98:case 106:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-2]),r.setTooltip(a[s-3],a[s]);break;case 99:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 100:this.$=a[s-4],r.setClickEvent(a[s-4],a[s-3],a[s-2]),r.setTooltip(a[s-4],a[s]);break;case 101:case 107:this.$=a[s-1],r.setLink(a[s-1],a[s]);break;case 102:case 108:this.$=a[s-3],r.setLink(a[s-3],a[s-2]),r.setTooltip(a[s-3],a[s]);break;case 103:case 109:this.$=a[s-3],r.setLink(a[s-3],a[s-2],a[s]);break;case 104:case 110:this.$=a[s-5],r.setLink(a[s-5],a[s-4],a[s]),r.setTooltip(a[s-5],a[s-2]);break;case 111:this.$=a[s-4],r.addVertex(a[s-2],void 0,void 0,a[s]);break;case 112:case 114:this.$=a[s-4],r.updateLink(a[s-2],a[s]);break;case 113:this.$=a[s-4],r.updateLink([a[s-2]],a[s]);break;case 115:this.$=a[s-8],r.updateLinkInterpolate([a[s-6]],a[s-2]),r.updateLink([a[s-6]],a[s]);break;case 116:this.$=a[s-8],r.updateLinkInterpolate(a[s-6],a[s-2]),r.updateLink(a[s-6],a[s]);break;case 117:this.$=a[s-6],r.updateLinkInterpolate([a[s-4]],a[s]);break;case 118:this.$=a[s-6],r.updateLinkInterpolate(a[s-4],a[s]);break;case 120:case 122:a[s-2].push(a[s]),this.$=a[s-2];break;case 124:this.$=a[s-1]+a[s];break;case 152:this.$="v";break;case 153:this.$="-";break;case 154:this.$={stmt:"dir",value:"TB"};break;case 155:this.$={stmt:"dir",value:"BT"};break;case 156:this.$={stmt:"dir",value:"RL"};break;case 157:this.$={stmt:"dir",value:"LR"}}},table:[{3:1,4:2,5:3,6:5,12:e,16:4,21:n,22:r,24:i},{1:[3]},{1:[2,1]},{3:10,4:2,5:3,6:5,12:e,16:4,21:n,22:r,24:i},t(a,o,{17:11}),{7:12,13:[1,13]},{16:14,21:n,22:r,24:i},{16:15,21:n,22:r,24:i},{25:[1,16],26:[1,17]},{13:[2,5]},{1:[2,2]},{1:[2,9],18:18,19:19,20:s,21:c,22:u,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,43:31,45:32,46:42,47:f,49:43,59:d,60:p,79:y,80:g,81:m,82:v,83:b,84:_,88:x,98:w,99:k,102:T,104:E,105:C,109:44,111:S,112:A,113:M,114:N,115:D,116:O,117:B,118:L,119:I,120:R},{8:61,10:[1,62],15:F},t([10,15],[2,6]),t(a,[2,17]),t(a,[2,18]),t(a,[2,19]),{20:[1,65],21:[1,66],22:P,27:64,30:67},t(j,[2,11]),t(j,[2,12]),t(j,[2,13]),t(j,[2,14]),t(j,[2,15]),t(j,[2,16]),{9:69,20:Y,21:z,23:U,44:70,71:74,74:[1,75],75:[1,76]},{9:77,20:Y,21:z,23:U},{9:78,20:Y,21:z,23:U},{9:79,20:Y,21:z,23:U},{9:80,20:Y,21:z,23:U},{9:81,20:Y,21:z,23:U},{9:83,20:Y,21:z,22:[1,82],23:U},t(j,[2,44]),t(q,[2,51],{30:84,22:P}),{22:[1,85]},{22:[1,86]},{22:[1,87]},{22:[1,88]},{26:H,47:$,59:W,60:V,77:[1,92],84:G,90:91,91:[1,89],93:[1,90],98:X,99:Z,102:K,104:Q,105:J,108:95,110:93,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(j,[2,154]),t(j,[2,155]),t(j,[2,156]),t(j,[2,157]),t(ot,[2,52],{48:[1,111]}),t(st,[2,70],{109:123,40:[1,112],47:f,50:[1,113],52:[1,114],54:[1,115],56:[1,116],58:[1,117],59:d,60:p,62:[1,118],64:[1,119],66:[1,120],67:[1,121],69:[1,122],84:_,88:x,98:w,99:k,102:T,104:E,105:C,115:D,116:O,117:B,118:L,119:I,120:R}),t(ct,[2,146]),t(ct,[2,171]),t(ct,[2,172]),t(ct,[2,173]),t(ct,[2,174]),t(ct,[2,175]),t(ct,[2,176]),t(ct,[2,177]),t(ct,[2,178]),t(ct,[2,179]),t(ct,[2,180]),t(ct,[2,181]),t(ct,[2,182]),t(ct,[2,183]),t(ct,[2,184]),t(ct,[2,185]),t(ct,[2,186]),{9:124,20:Y,21:z,23:U},{11:125,14:[1,126]},t(ut,[2,8]),t(a,[2,20]),t(a,[2,26]),t(a,[2,27]),{21:[1,127]},t(lt,[2,34],{30:128,22:P}),t(j,[2,35]),{45:129,46:42,47:f,49:43,59:d,60:p,84:_,88:x,98:w,99:k,102:T,104:E,105:C,109:44,115:D,116:O,117:B,118:L,119:I,120:R},t(ht,[2,45]),t(ht,[2,46]),t(ht,[2,47]),t(ft,[2,74],{72:130,61:[1,132],73:[1,131]}),{22:dt,24:pt,26:yt,38:gt,39:133,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},t([47,59,60,61,73,84,88,98,99,102,104,105,115,116,117,118,119,120],[2,76]),t(j,[2,36]),t(j,[2,37]),t(j,[2,38]),t(j,[2,39]),t(j,[2,40]),{22:dt,24:pt,26:yt,38:gt,39:157,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(Ot,o,{17:158}),t(q,[2,50],{47:Bt}),{26:H,47:$,59:W,60:V,84:G,90:160,95:[1,161],98:X,99:Z,102:K,104:Q,105:J,108:95,110:93,115:tt,116:et,117:nt,118:rt,119:it,120:at},{88:[1,162],96:163,98:[1,164]},{26:H,47:$,59:W,60:V,84:G,88:[1,165],90:166,98:X,99:Z,102:K,104:Q,105:J,108:95,110:93,115:tt,116:et,117:nt,118:rt,119:it,120:at},{26:H,47:$,59:W,60:V,84:G,90:167,98:X,99:Z,102:K,104:Q,105:J,108:95,110:93,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(ut,[2,97],{22:[1,168],92:[1,169]}),t(ut,[2,101],{22:[1,170]}),t(ut,[2,105],{108:95,110:172,22:[1,171],26:H,47:$,59:W,60:V,84:G,98:X,99:Z,102:K,104:Q,105:J,115:tt,116:et,117:nt,118:rt,119:it,120:at}),t(ut,[2,107],{22:[1,173]}),t(Lt,[2,148]),t(Lt,[2,150]),t(Lt,[2,151]),t(Lt,[2,152]),t(Lt,[2,153]),t(It,[2,158]),t(It,[2,159]),t(It,[2,160]),t(It,[2,161]),t(It,[2,162]),t(It,[2,163]),t(It,[2,164]),t(It,[2,165]),t(It,[2,166]),t(It,[2,167]),t(It,[2,168]),t(It,[2,169]),t(It,[2,170]),{47:f,49:174,59:d,60:p,84:_,88:x,98:w,99:k,102:T,104:E,105:C,109:44,115:D,116:O,117:B,118:L,119:I,120:R},{22:dt,24:pt,26:yt,38:gt,39:175,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,39:177,42:mt,47:$,50:[1,176],59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,39:178,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,39:179,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,39:180,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{59:[1,181]},{22:dt,24:pt,26:yt,38:gt,39:182,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,39:183,42:mt,47:$,59:W,60:V,64:[1,184],66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,39:185,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,39:186,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,39:187,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(ct,[2,147]),t(Rt,[2,3]),{8:188,15:F},{15:[2,7]},t(a,[2,28]),t(lt,[2,33]),t(q,[2,48],{30:189,22:P}),t(ft,[2,71],{22:[1,190]}),{22:[1,191]},{22:dt,24:pt,26:yt,38:gt,39:192,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,59:W,60:V,66:vt,74:bt,75:[1,193],76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(It,[2,78]),t(It,[2,80]),t(It,[2,136]),t(It,[2,137]),t(It,[2,138]),t(It,[2,139]),t(It,[2,140]),t(It,[2,141]),t(It,[2,142]),t(It,[2,143]),t(It,[2,144]),t(It,[2,145]),t(It,[2,81]),t(It,[2,82]),t(It,[2,83]),t(It,[2,84]),t(It,[2,85]),t(It,[2,86]),t(It,[2,87]),t(It,[2,88]),t(It,[2,89]),t(It,[2,90]),t(It,[2,91]),{9:196,20:Y,21:z,22:dt,23:U,24:pt,26:yt,38:gt,40:[1,195],42:mt,47:$,59:W,60:V,66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{18:18,19:19,20:s,21:c,22:u,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,197],43:31,45:32,46:42,47:f,49:43,59:d,60:p,79:y,80:g,81:m,82:v,83:b,84:_,88:x,98:w,99:k,102:T,104:E,105:C,109:44,111:S,112:A,113:M,114:N,115:D,116:O,117:B,118:L,119:I,120:R},{22:P,30:198},{22:[1,199],26:H,47:$,59:W,60:V,84:G,98:X,99:Z,102:K,104:Q,105:J,108:95,110:172,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:[1,200]},{22:[1,201]},{22:[1,202],99:[1,203]},t(Ft,[2,119]),{22:[1,204]},{22:[1,205],26:H,47:$,59:W,60:V,84:G,98:X,99:Z,102:K,104:Q,105:J,108:95,110:172,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:[1,206],26:H,47:$,59:W,60:V,84:G,98:X,99:Z,102:K,104:Q,105:J,108:95,110:172,115:tt,116:et,117:nt,118:rt,119:it,120:at},{77:[1,207]},t(ut,[2,99],{22:[1,208]}),{77:[1,209],94:[1,210]},{77:[1,211]},t(Lt,[2,149]),{77:[1,212],94:[1,213]},t(ot,[2,54],{109:123,47:f,59:d,60:p,84:_,88:x,98:w,99:k,102:T,104:E,105:C,115:D,116:O,117:B,118:L,119:I,120:R}),{22:dt,24:pt,26:yt,38:gt,41:[1,214],42:mt,47:$,59:W,60:V,66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,39:215,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,51:[1,216],59:W,60:V,66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,53:[1,217],59:W,60:V,66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,55:[1,218],59:W,60:V,66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,57:[1,219],59:W,60:V,66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{60:[1,220]},{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,59:W,60:V,63:[1,221],66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,59:W,60:V,65:[1,222],66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,39:223,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,41:[1,224],42:mt,47:$,59:W,60:V,66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,59:W,60:V,66:vt,68:[1,225],70:[1,226],74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,59:W,60:V,66:vt,68:[1,228],70:[1,227],74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{9:229,20:Y,21:z,23:U},t(q,[2,49],{47:Bt}),t(ft,[2,73]),t(ft,[2,72]),{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,59:W,60:V,61:[1,230],66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(ft,[2,75]),t(It,[2,79]),{22:dt,24:pt,26:yt,38:gt,39:231,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(Ot,o,{17:232}),t(j,[2,43]),{46:233,47:f,49:43,59:d,60:p,84:_,88:x,98:w,99:k,102:T,104:E,105:C,109:44,115:D,116:O,117:B,118:L,119:I,120:R},{22:Pt,59:jt,60:Yt,79:zt,89:234,95:Ut,98:qt,100:235,101:236,102:Ht,103:$t,104:Wt,105:Vt,106:Gt},{22:Pt,59:jt,60:Yt,79:zt,89:248,95:Ut,98:qt,100:235,101:236,102:Ht,103:$t,104:Wt,105:Vt,106:Gt},{22:Pt,59:jt,60:Yt,79:zt,89:249,95:Ut,97:[1,250],98:qt,100:235,101:236,102:Ht,103:$t,104:Wt,105:Vt,106:Gt},{22:Pt,59:jt,60:Yt,79:zt,89:251,95:Ut,97:[1,252],98:qt,100:235,101:236,102:Ht,103:$t,104:Wt,105:Vt,106:Gt},{98:[1,253]},{22:Pt,59:jt,60:Yt,79:zt,89:254,95:Ut,98:qt,100:235,101:236,102:Ht,103:$t,104:Wt,105:Vt,106:Gt},{22:Pt,59:jt,60:Yt,79:zt,89:255,95:Ut,98:qt,100:235,101:236,102:Ht,103:$t,104:Wt,105:Vt,106:Gt},{26:H,47:$,59:W,60:V,84:G,90:256,98:X,99:Z,102:K,104:Q,105:J,108:95,110:93,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(ut,[2,98]),{77:[1,257]},t(ut,[2,102],{22:[1,258]}),t(ut,[2,103]),t(ut,[2,106]),t(ut,[2,108],{22:[1,259]}),t(ut,[2,109]),t(st,[2,55]),{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,51:[1,260],59:W,60:V,66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(st,[2,62]),t(st,[2,57]),t(st,[2,58]),t(st,[2,59]),{59:[1,261]},t(st,[2,61]),t(st,[2,63]),{22:dt,24:pt,26:yt,38:gt,42:mt,47:$,59:W,60:V,65:[1,262],66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(st,[2,65]),t(st,[2,66]),t(st,[2,68]),t(st,[2,67]),t(st,[2,69]),t(Rt,[2,4]),t([22,47,59,60,84,88,98,99,102,104,105,115,116,117,118,119,120],[2,77]),{22:dt,24:pt,26:yt,38:gt,41:[1,263],42:mt,47:$,59:W,60:V,66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{18:18,19:19,20:s,21:c,22:u,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,264],43:31,45:32,46:42,47:f,49:43,59:d,60:p,79:y,80:g,81:m,82:v,83:b,84:_,88:x,98:w,99:k,102:T,104:E,105:C,109:44,111:S,112:A,113:M,114:N,115:D,116:O,117:B,118:L,119:I,120:R},t(ot,[2,53]),t(ut,[2,111],{99:Xt}),t(Zt,[2,121],{101:266,22:Pt,59:jt,60:Yt,79:zt,95:Ut,98:qt,102:Ht,103:$t,104:Wt,105:Vt,106:Gt}),t(Kt,[2,123]),t(Kt,[2,125]),t(Kt,[2,126]),t(Kt,[2,127]),t(Kt,[2,128]),t(Kt,[2,129]),t(Kt,[2,130]),t(Kt,[2,131]),t(Kt,[2,132]),t(Kt,[2,133]),t(Kt,[2,134]),t(Kt,[2,135]),t(ut,[2,112],{99:Xt}),t(ut,[2,113],{99:Xt}),{22:[1,267]},t(ut,[2,114],{99:Xt}),{22:[1,268]},t(Ft,[2,120]),t(ut,[2,94],{99:Xt}),t(ut,[2,95],{99:Xt}),t(ut,[2,96],{108:95,110:172,26:H,47:$,59:W,60:V,84:G,98:X,99:Z,102:K,104:Q,105:J,115:tt,116:et,117:nt,118:rt,119:it,120:at}),t(ut,[2,100]),{94:[1,269]},{94:[1,270]},{51:[1,271]},{61:[1,272]},{65:[1,273]},{9:274,20:Y,21:z,23:U},t(j,[2,42]),{22:Pt,59:jt,60:Yt,79:zt,95:Ut,98:qt,100:275,101:236,102:Ht,103:$t,104:Wt,105:Vt,106:Gt},t(Kt,[2,124]),{26:H,47:$,59:W,60:V,84:G,90:276,98:X,99:Z,102:K,104:Q,105:J,108:95,110:93,115:tt,116:et,117:nt,118:rt,119:it,120:at},{26:H,47:$,59:W,60:V,84:G,90:277,98:X,99:Z,102:K,104:Q,105:J,108:95,110:93,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(ut,[2,104]),t(ut,[2,110]),t(st,[2,56]),{22:dt,24:pt,26:yt,38:gt,39:278,42:mt,47:$,59:W,60:V,66:vt,74:bt,76:134,77:_t,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},t(st,[2,64]),t(Ot,o,{17:279}),t(Zt,[2,122],{101:266,22:Pt,59:jt,60:Yt,79:zt,95:Ut,98:qt,102:Ht,103:$t,104:Wt,105:Vt,106:Gt}),t(ut,[2,117],{108:95,110:172,22:[1,280],26:H,47:$,59:W,60:V,84:G,98:X,99:Z,102:K,104:Q,105:J,115:tt,116:et,117:nt,118:rt,119:it,120:at}),t(ut,[2,118],{108:95,110:172,22:[1,281],26:H,47:$,59:W,60:V,84:G,98:X,99:Z,102:K,104:Q,105:J,115:tt,116:et,117:nt,118:rt,119:it,120:at}),{22:dt,24:pt,26:yt,38:gt,41:[1,282],42:mt,47:$,59:W,60:V,66:vt,74:bt,76:194,78:145,79:xt,80:wt,81:kt,82:Tt,83:Et,84:Ct,85:St,87:136,88:At,98:X,99:Z,102:Mt,104:Q,105:J,106:Nt,107:Dt,108:142,115:tt,116:et,117:nt,118:rt,119:it,120:at},{18:18,19:19,20:s,21:c,22:u,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,283],43:31,45:32,46:42,47:f,49:43,59:d,60:p,79:y,80:g,81:m,82:v,83:b,84:_,88:x,98:w,99:k,102:T,104:E,105:C,109:44,111:S,112:A,113:M,114:N,115:D,116:O,117:B,118:L,119:I,120:R},{22:Pt,59:jt,60:Yt,79:zt,89:284,95:Ut,98:qt,100:235,101:236,102:Ht,103:$t,104:Wt,105:Vt,106:Gt},{22:Pt,59:jt,60:Yt,79:zt,89:285,95:Ut,98:qt,100:235,101:236,102:Ht,103:$t,104:Wt,105:Vt,106:Gt},t(st,[2,60]),t(j,[2,41]),t(ut,[2,115],{99:Xt}),t(ut,[2,116],{99:Xt})],defaultActions:{2:[2,1],9:[2,5],10:[2,2],126:[2,7]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=a.slice.call(arguments,1),f=Object.create(this.lexer),d={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(d.yy[p]=this.yy[p]);f.setInput(t,d.yy),d.yy.lexer=f,d.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var y=f.yylloc;a.push(y);var g=f.options&&f.options.ranges;function m(){var t;return"number"!=typeof(t=r.pop()||f.lex()||1)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof d.yy.parseError?this.parseError=d.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var v,b,_,x,w,k,T,E,C,S={};;){if(_=n[n.length-1],this.defaultActions[_]?x=this.defaultActions[_]:(null==v&&(v=m()),x=o[_]&&o[_][v]),void 0===x||!x.length||!x[0]){var A;for(k in C=[],o[_])this.terminals_[k]&&k>2&&C.push("'"+this.terminals_[k]+"'");A=f.showPosition?"Parse error on line "+(c+1)+":\n"+f.showPosition()+"\nExpecting "+C.join(", ")+", got '"+(this.terminals_[v]||v)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==v?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(A,{text:f.match,token:this.terminals_[v]||v,line:f.yylineno,loc:y,expected:C})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+v);switch(x[0]){case 1:n.push(v),i.push(f.yytext),a.push(f.yylloc),n.push(x[1]),v=null,b?(v=b,b=null):(u=f.yyleng,s=f.yytext,c=f.yylineno,y=f.yylloc,l>0&&l--);break;case 2:if(T=this.productions_[x[1]][1],S.$=i[i.length-T],S._$={first_line:a[a.length-(T||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(T||1)].first_column,last_column:a[a.length-1].last_column},g&&(S._$.range=[a[a.length-(T||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[s,u,c,d.yy,x[1],i,a].concat(h))))return w;T&&(n=n.slice(0,-1*T*2),i=i.slice(0,-1*T),a=a.slice(0,-1*T)),n.push(this.productions_[x[1]][0]),i.push(S.$),a.push(S._$),E=o[n[n.length-2]][n[n.length-1]],n.push(E);break;case 3:return!0}}return!0}},Jt={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),12;case 1:return this.begin("type_directive"),13;case 2:return this.popState(),this.begin("arg_directive"),10;case 3:return this.popState(),this.popState(),15;case 4:return 14;case 5:case 6:break;case 7:this.begin("string");break;case 8:case 17:case 20:case 23:case 26:this.popState();break;case 9:return"STR";case 10:return 79;case 11:return 88;case 12:return 80;case 13:return 97;case 14:return 81;case 15:return 82;case 16:this.begin("href");break;case 18:return 93;case 19:this.begin("callbackname");break;case 21:this.popState(),this.begin("callbackargs");break;case 22:return 91;case 24:return 92;case 25:this.begin("click");break;case 27:return 83;case 28:case 29:return t.lex.firstGraph()&&this.begin("dir"),24;case 30:return 38;case 31:return 42;case 32:case 33:case 34:case 35:return 94;case 36:return this.popState(),25;case 37:case 38:case 39:case 40:case 41:case 42:case 43:case 44:case 45:case 46:return this.popState(),26;case 47:return 111;case 48:return 112;case 49:return 113;case 50:return 114;case 51:return 98;case 52:return 104;case 53:return 48;case 54:return 60;case 55:return 47;case 56:return 20;case 57:return 99;case 58:return 119;case 59:case 60:case 61:return 75;case 62:case 63:case 64:return 74;case 65:return 52;case 66:return 53;case 67:return 54;case 68:return 55;case 69:return 56;case 70:return 57;case 71:return 58;case 72:return 62;case 73:return 63;case 74:return 102;case 75:return 105;case 76:return 120;case 77:return 117;case 78:return 106;case 79:case 80:return 118;case 81:return 107;case 82:return 66;case 83:return 85;case 84:return"SEP";case 85:return 84;case 86:return 59;case 87:return 68;case 88:return 67;case 89:return 70;case 90:return 69;case 91:return 115;case 92:return 116;case 93:return 61;case 94:return 50;case 95:return 51;case 96:return 40;case 97:return 41;case 98:return 64;case 99:return 65;case 100:return 126;case 101:return 21;case 102:return 22;case 103:return 23}},rules:[/^(?:%%\{)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)[^\n]*)/,/^(?:[^\}]%%[^\n]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:style\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\b)/,/^(?:class\b)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:click[\s]+)/,/^(?:[\s\n])/,/^(?:[^\s\n]*)/,/^(?:graph\b)/,/^(?:flowchart\b)/,/^(?:subgraph\b)/,/^(?:end\b\s*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:(\r?\n)*\s*\n)/,/^(?:\s*LR\b)/,/^(?:\s*RL\b)/,/^(?:\s*TB\b)/,/^(?:\s*BT\b)/,/^(?:\s*TD\b)/,/^(?:\s*BR\b)/,/^(?:\s*<)/,/^(?:\s*>)/,/^(?:\s*\^)/,/^(?:\s*v\b)/,/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:[0-9]+)/,/^(?:#)/,/^(?::::)/,/^(?::)/,/^(?:&)/,/^(?:;)/,/^(?:,)/,/^(?:\*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:\(-)/,/^(?:-\))/,/^(?:\(\[)/,/^(?:\]\))/,/^(?:\[\[)/,/^(?:\]\])/,/^(?:\[\|)/,/^(?:\[\()/,/^(?:\)\])/,/^(?:-)/,/^(?:\.)/,/^(?:[\_])/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:<)/,/^(?:>)/,/^(?:\^)/,/^(?:\\\|)/,/^(?:v\b)/,/^(?:[A-Za-z]+)/,/^(?:\\\])/,/^(?:\[\/)/,/^(?:\/\])/,/^(?:\[\\)/,/^(?:[!"#$%&'*+,-.`?\\_/])/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\|)/,/^(?:\()/,/^(?:\))/,/^(?:\[)/,/^(?:\])/,/^(?:\{)/,/^(?:\})/,/^(?:")/,/^(?:(\r?\n)+)/,/^(?:\s)/,/^(?:$)/],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[23,24],inclusive:!1},callbackname:{rules:[20,21,22],inclusive:!1},href:{rules:[17,18],inclusive:!1},click:{rules:[26,27],inclusive:!1},vertex:{rules:[],inclusive:!1},dir:{rules:[36,37,38,39,40,41,42,43,44,45,46],inclusive:!1},string:{rules:[8,9],inclusive:!1},INITIAL:{rules:[0,5,6,7,10,11,12,13,14,15,16,19,25,28,29,30,31,32,33,34,35,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103],inclusive:!0}}};function te(){this.yy={}}return Qt.lexer=Jt,te.prototype=Qt,Qt.Parser=te,new te}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(5354).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},9959:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,3],n=[1,5],r=[7,9,11,12,13,14,15,16,17,18,19,20,22,29,34],i=[1,15],a=[1,16],o=[1,17],s=[1,18],c=[1,19],u=[1,20],l=[1,21],h=[1,22],f=[1,23],d=[1,25],p=[1,27],y=[1,30],g=[5,7,9,11,12,13,14,15,16,17,18,19,20,22,29,34],m={trace:function(){},yy:{},symbols_:{error:2,start:3,directive:4,gantt:5,document:6,EOF:7,line:8,SPACE:9,statement:10,NL:11,dateFormat:12,inclusiveEndDates:13,topAxis:14,axisFormat:15,excludes:16,includes:17,todayMarker:18,title:19,section:20,clickStatement:21,taskTxt:22,taskData:23,openDirective:24,typeDirective:25,closeDirective:26,":":27,argDirective:28,click:29,callbackname:30,callbackargs:31,href:32,clickStatementDebug:33,open_directive:34,type_directive:35,arg_directive:36,close_directive:37,$accept:0,$end:1},terminals_:{2:"error",5:"gantt",7:"EOF",9:"SPACE",11:"NL",12:"dateFormat",13:"inclusiveEndDates",14:"topAxis",15:"axisFormat",16:"excludes",17:"includes",18:"todayMarker",19:"title",20:"section",22:"taskTxt",23:"taskData",27:":",29:"click",30:"callbackname",31:"callbackargs",32:"href",34:"open_directive",35:"type_directive",36:"arg_directive",37:"close_directive"},productions_:[0,[3,2],[3,3],[6,0],[6,2],[8,2],[8,1],[8,1],[8,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,2],[10,1],[4,4],[4,6],[21,2],[21,3],[21,3],[21,4],[21,3],[21,4],[21,2],[33,2],[33,3],[33,3],[33,4],[33,3],[33,4],[33,2],[24,1],[25,1],[28,1],[26,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 2:return a[s-1];case 3:case 7:case 8:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 9:r.setDateFormat(a[s].substr(11)),this.$=a[s].substr(11);break;case 10:r.enableInclusiveEndDates(),this.$=a[s].substr(18);break;case 11:r.TopAxis(),this.$=a[s].substr(8);break;case 12:r.setAxisFormat(a[s].substr(11)),this.$=a[s].substr(11);break;case 13:r.setExcludes(a[s].substr(9)),this.$=a[s].substr(9);break;case 14:r.setIncludes(a[s].substr(9)),this.$=a[s].substr(9);break;case 15:r.setTodayMarker(a[s].substr(12)),this.$=a[s].substr(12);break;case 16:r.setTitle(a[s].substr(6)),this.$=a[s].substr(6);break;case 17:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 19:r.addTask(a[s-1],a[s]),this.$="task";break;case 23:this.$=a[s-1],r.setClickEvent(a[s-1],a[s],null);break;case 24:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 25:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],null),r.setLink(a[s-2],a[s]);break;case 26:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-2],a[s-1]),r.setLink(a[s-3],a[s]);break;case 27:this.$=a[s-2],r.setClickEvent(a[s-2],a[s],null),r.setLink(a[s-2],a[s-1]);break;case 28:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-1],a[s]),r.setLink(a[s-3],a[s-2]);break;case 29:this.$=a[s-1],r.setLink(a[s-1],a[s]);break;case 30:case 36:this.$=a[s-1]+" "+a[s];break;case 31:case 32:case 34:this.$=a[s-2]+" "+a[s-1]+" "+a[s];break;case 33:case 35:this.$=a[s-3]+" "+a[s-2]+" "+a[s-1]+" "+a[s];break;case 37:r.parseDirective("%%{","open_directive");break;case 38:r.parseDirective(a[s],"type_directive");break;case 39:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 40:r.parseDirective("}%%","close_directive","gantt")}},table:[{3:1,4:2,5:e,24:4,34:n},{1:[3]},{3:6,4:2,5:e,24:4,34:n},t(r,[2,3],{6:7}),{25:8,35:[1,9]},{35:[2,37]},{1:[2,1]},{4:26,7:[1,10],8:11,9:[1,12],10:13,11:[1,14],12:i,13:a,14:o,15:s,16:c,17:u,18:l,19:h,20:f,21:24,22:d,24:4,29:p,34:n},{26:28,27:[1,29],37:y},t([27,37],[2,38]),t(r,[2,8],{1:[2,2]}),t(r,[2,4]),{4:26,10:31,12:i,13:a,14:o,15:s,16:c,17:u,18:l,19:h,20:f,21:24,22:d,24:4,29:p,34:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,9]),t(r,[2,10]),t(r,[2,11]),t(r,[2,12]),t(r,[2,13]),t(r,[2,14]),t(r,[2,15]),t(r,[2,16]),t(r,[2,17]),t(r,[2,18]),{23:[1,32]},t(r,[2,20]),{30:[1,33],32:[1,34]},{11:[1,35]},{28:36,36:[1,37]},{11:[2,40]},t(r,[2,5]),t(r,[2,19]),t(r,[2,23],{31:[1,38],32:[1,39]}),t(r,[2,29],{30:[1,40]}),t(g,[2,21]),{26:41,37:y},{37:[2,39]},t(r,[2,24],{32:[1,42]}),t(r,[2,25]),t(r,[2,27],{31:[1,43]}),{11:[1,44]},t(r,[2,26]),t(r,[2,28]),t(g,[2,22])],defaultActions:{5:[2,37],6:[2,1],30:[2,40],37:[2,39]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=a.slice.call(arguments,1),f=Object.create(this.lexer),d={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(d.yy[p]=this.yy[p]);f.setInput(t,d.yy),d.yy.lexer=f,d.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var y=f.yylloc;a.push(y);var g=f.options&&f.options.ranges;function m(){var t;return"number"!=typeof(t=r.pop()||f.lex()||1)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof d.yy.parseError?this.parseError=d.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var v,b,_,x,w,k,T,E,C,S={};;){if(_=n[n.length-1],this.defaultActions[_]?x=this.defaultActions[_]:(null==v&&(v=m()),x=o[_]&&o[_][v]),void 0===x||!x.length||!x[0]){var A;for(k in C=[],o[_])this.terminals_[k]&&k>2&&C.push("'"+this.terminals_[k]+"'");A=f.showPosition?"Parse error on line "+(c+1)+":\n"+f.showPosition()+"\nExpecting "+C.join(", ")+", got '"+(this.terminals_[v]||v)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==v?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(A,{text:f.match,token:this.terminals_[v]||v,line:f.yylineno,loc:y,expected:C})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+v);switch(x[0]){case 1:n.push(v),i.push(f.yytext),a.push(f.yylloc),n.push(x[1]),v=null,b?(v=b,b=null):(u=f.yyleng,s=f.yytext,c=f.yylineno,y=f.yylloc,l>0&&l--);break;case 2:if(T=this.productions_[x[1]][1],S.$=i[i.length-T],S._$={first_line:a[a.length-(T||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(T||1)].first_column,last_column:a[a.length-1].last_column},g&&(S._$.range=[a[a.length-(T||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[s,u,c,d.yy,x[1],i,a].concat(h))))return w;T&&(n=n.slice(0,-1*T*2),i=i.slice(0,-1*T),a=a.slice(0,-1*T)),n.push(this.productions_[x[1]][0]),i.push(S.$),a.push(S._$),E=o[n[n.length-2]][n[n.length-1]],n.push(E);break;case 3:return!0}}return!0}},v={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),34;case 1:return this.begin("type_directive"),35;case 2:return this.popState(),this.begin("arg_directive"),27;case 3:return this.popState(),this.popState(),37;case 4:return 36;case 5:case 6:case 7:case 9:case 10:case 11:break;case 8:return 11;case 12:this.begin("href");break;case 13:case 16:case 19:case 22:this.popState();break;case 14:return 32;case 15:this.begin("callbackname");break;case 17:this.popState(),this.begin("callbackargs");break;case 18:return 30;case 20:return 31;case 21:this.begin("click");break;case 23:return 29;case 24:return 5;case 25:return 12;case 26:return 13;case 27:return 14;case 28:return 15;case 29:return 17;case 30:return 16;case 31:return 18;case 32:return"date";case 33:return 19;case 34:return 20;case 35:return 22;case 36:return 23;case 37:return 27;case 38:return 7;case 39:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)*[^\n]*)/i,/^(?:[^\}]%%*[^\n]*)/i,/^(?:%%*[^\n]*[\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:href[\s]+["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:call[\s]+)/i,/^(?:\([\s]*\))/i,/^(?:\()/i,/^(?:[^(]*)/i,/^(?:\))/i,/^(?:[^)]*)/i,/^(?:click[\s]+)/i,/^(?:[\s\n])/i,/^(?:[^\s\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:inclusiveEndDates\b)/i,/^(?:topAxis\b)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:includes\s[^#\n;]+)/i,/^(?:excludes\s[^#\n;]+)/i,/^(?:todayMarker\s[^\n;]+)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[19,20],inclusive:!1},callbackname:{rules:[16,17,18],inclusive:!1},href:{rules:[13,14],inclusive:!1},click:{rules:[22,23],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,15,21,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39],inclusive:!0}}};function b(){this.yy={}}return m.lexer=v,b.prototype=m,m.Parser=b,new b}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(6878).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},2553:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[2,3],n=[1,7],r=[7,12,15,17,19,20,21],i=[7,11,12,15,17,19,20,21],a=[2,20],o=[1,32],s={trace:function(){},yy:{},symbols_:{error:2,start:3,GG:4,":":5,document:6,EOF:7,DIR:8,options:9,body:10,OPT:11,NL:12,line:13,statement:14,COMMIT:15,commit_arg:16,BRANCH:17,ID:18,CHECKOUT:19,MERGE:20,RESET:21,reset_arg:22,STR:23,HEAD:24,reset_parents:25,CARET:26,$accept:0,$end:1},terminals_:{2:"error",4:"GG",5:":",7:"EOF",8:"DIR",11:"OPT",12:"NL",15:"COMMIT",17:"BRANCH",18:"ID",19:"CHECKOUT",20:"MERGE",21:"RESET",23:"STR",24:"HEAD",26:"CARET"},productions_:[0,[3,4],[3,5],[6,0],[6,2],[9,2],[9,1],[10,0],[10,2],[13,2],[13,1],[14,2],[14,2],[14,2],[14,2],[14,2],[16,0],[16,1],[22,2],[22,2],[25,0],[25,2]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:return a[s-1];case 2:return r.setDirection(a[s-3]),a[s-1];case 4:r.setOptions(a[s-1]),this.$=a[s];break;case 5:a[s-1]+=a[s],this.$=a[s-1];break;case 7:this.$=[];break;case 8:a[s-1].push(a[s]),this.$=a[s-1];break;case 9:this.$=a[s-1];break;case 11:r.commit(a[s]);break;case 12:r.branch(a[s]);break;case 13:r.checkout(a[s]);break;case 14:r.merge(a[s]);break;case 15:r.reset(a[s]);break;case 16:this.$="";break;case 17:this.$=a[s];break;case 18:this.$=a[s-1]+":"+a[s];break;case 19:this.$=a[s-1]+":"+r.count,r.count=0;break;case 20:r.count=0;break;case 21:r.count+=1}},table:[{3:1,4:[1,2]},{1:[3]},{5:[1,3],8:[1,4]},{6:5,7:e,9:6,12:n},{5:[1,8]},{7:[1,9]},t(r,[2,7],{10:10,11:[1,11]}),t(i,[2,6]),{6:12,7:e,9:6,12:n},{1:[2,1]},{7:[2,4],12:[1,15],13:13,14:14,15:[1,16],17:[1,17],19:[1,18],20:[1,19],21:[1,20]},t(i,[2,5]),{7:[1,21]},t(r,[2,8]),{12:[1,22]},t(r,[2,10]),{12:[2,16],16:23,23:[1,24]},{18:[1,25]},{18:[1,26]},{18:[1,27]},{18:[1,30],22:28,24:[1,29]},{1:[2,2]},t(r,[2,9]),{12:[2,11]},{12:[2,17]},{12:[2,12]},{12:[2,13]},{12:[2,14]},{12:[2,15]},{12:a,25:31,26:o},{12:a,25:33,26:o},{12:[2,18]},{12:a,25:34,26:o},{12:[2,19]},{12:[2,21]}],defaultActions:{9:[2,1],21:[2,2],23:[2,11],24:[2,17],25:[2,12],26:[2,13],27:[2,14],28:[2,15],31:[2,18],33:[2,19],34:[2,21]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=a.slice.call(arguments,1),f=Object.create(this.lexer),d={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(d.yy[p]=this.yy[p]);f.setInput(t,d.yy),d.yy.lexer=f,d.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var y=f.yylloc;a.push(y);var g=f.options&&f.options.ranges;function m(){var t;return"number"!=typeof(t=r.pop()||f.lex()||1)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof d.yy.parseError?this.parseError=d.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var v,b,_,x,w,k,T,E,C,S={};;){if(_=n[n.length-1],this.defaultActions[_]?x=this.defaultActions[_]:(null==v&&(v=m()),x=o[_]&&o[_][v]),void 0===x||!x.length||!x[0]){var A;for(k in C=[],o[_])this.terminals_[k]&&k>2&&C.push("'"+this.terminals_[k]+"'");A=f.showPosition?"Parse error on line "+(c+1)+":\n"+f.showPosition()+"\nExpecting "+C.join(", ")+", got '"+(this.terminals_[v]||v)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==v?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(A,{text:f.match,token:this.terminals_[v]||v,line:f.yylineno,loc:y,expected:C})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+v);switch(x[0]){case 1:n.push(v),i.push(f.yytext),a.push(f.yylloc),n.push(x[1]),v=null,b?(v=b,b=null):(u=f.yyleng,s=f.yytext,c=f.yylineno,y=f.yylloc,l>0&&l--);break;case 2:if(T=this.productions_[x[1]][1],S.$=i[i.length-T],S._$={first_line:a[a.length-(T||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(T||1)].first_column,last_column:a[a.length-1].last_column},g&&(S._$.range=[a[a.length-(T||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[s,u,c,d.yy,x[1],i,a].concat(h))))return w;T&&(n=n.slice(0,-1*T*2),i=i.slice(0,-1*T),a=a.slice(0,-1*T)),n.push(this.productions_[x[1]][0]),i.push(S.$),a.push(S._$),E=o[n[n.length-2]][n[n.length-1]],n.push(E);break;case 3:return!0}}return!0}},c={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 12;case 1:case 2:case 3:break;case 4:return 4;case 5:return 15;case 6:return 17;case 7:return 20;case 8:return 21;case 9:return 19;case 10:case 11:return 8;case 12:return 5;case 13:return 26;case 14:this.begin("options");break;case 15:case 18:this.popState();break;case 16:return 11;case 17:this.begin("string");break;case 19:return 23;case 20:return 18;case 21:return 7}},rules:[/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:gitGraph\b)/i,/^(?:commit\b)/i,/^(?:branch\b)/i,/^(?:merge\b)/i,/^(?:reset\b)/i,/^(?:checkout\b)/i,/^(?:LR\b)/i,/^(?:BT\b)/i,/^(?::)/i,/^(?:\^)/i,/^(?:options\r?\n)/i,/^(?:end\r?\n)/i,/^(?:[^\n]+\r?\n)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[a-zA-Z][-_\.a-zA-Z0-9]*[-_a-zA-Z0-9])/i,/^(?:$)/i],conditions:{options:{rules:[15,16],inclusive:!1},string:{rules:[18,19],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,17,20,21],inclusive:!0}}};function u(){this.yy={}}return s.lexer=c,u.prototype=s,s.Parser=u,new u}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(8183).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},6765:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[6,9,10],n={trace:function(){},yy:{},symbols_:{error:2,start:3,info:4,document:5,EOF:6,line:7,statement:8,NL:9,showInfo:10,$accept:0,$end:1},terminals_:{2:"error",4:"info",6:"EOF",9:"NL",10:"showInfo"},productions_:[0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,1]],performAction:function(t,e,n,r,i,a,o){switch(a.length,i){case 1:return r;case 4:break;case 6:r.setInfo(!0)}},table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:6,9:[1,7],10:[1,8]},{1:[2,1]},t(e,[2,3]),t(e,[2,4]),t(e,[2,5]),t(e,[2,6])],defaultActions:{4:[2,1]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=a.slice.call(arguments,1),f=Object.create(this.lexer),d={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(d.yy[p]=this.yy[p]);f.setInput(t,d.yy),d.yy.lexer=f,d.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var y=f.yylloc;a.push(y);var g=f.options&&f.options.ranges;function m(){var t;return"number"!=typeof(t=r.pop()||f.lex()||1)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof d.yy.parseError?this.parseError=d.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var v,b,_,x,w,k,T,E,C,S={};;){if(_=n[n.length-1],this.defaultActions[_]?x=this.defaultActions[_]:(null==v&&(v=m()),x=o[_]&&o[_][v]),void 0===x||!x.length||!x[0]){var A;for(k in C=[],o[_])this.terminals_[k]&&k>2&&C.push("'"+this.terminals_[k]+"'");A=f.showPosition?"Parse error on line "+(c+1)+":\n"+f.showPosition()+"\nExpecting "+C.join(", ")+", got '"+(this.terminals_[v]||v)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==v?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(A,{text:f.match,token:this.terminals_[v]||v,line:f.yylineno,loc:y,expected:C})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+v);switch(x[0]){case 1:n.push(v),i.push(f.yytext),a.push(f.yylloc),n.push(x[1]),v=null,b?(v=b,b=null):(u=f.yyleng,s=f.yytext,c=f.yylineno,y=f.yylloc,l>0&&l--);break;case 2:if(T=this.productions_[x[1]][1],S.$=i[i.length-T],S._$={first_line:a[a.length-(T||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(T||1)].first_column,last_column:a[a.length-1].last_column},g&&(S._$.range=[a[a.length-(T||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[s,u,c,d.yy,x[1],i,a].concat(h))))return w;T&&(n=n.slice(0,-1*T*2),i=i.slice(0,-1*T),a=a.slice(0,-1*T)),n.push(this.productions_[x[1]][0]),i.push(S.$),a.push(S._$),E=o[n[n.length-2]][n[n.length-1]],n.push(E);break;case 3:return!0}}return!0}},r={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 4;case 1:return 9;case 2:return"space";case 3:return 10;case 4:return 6;case 5:return"TXT"}},rules:[/^(?:info\b)/i,/^(?:[\s\n\r]+)/i,/^(?:[\s]+)/i,/^(?:showInfo\b)/i,/^(?:$)/i,/^(?:.)/i],conditions:{INITIAL:{rules:[0,1,2,3,4,5],inclusive:!0}}};function i(){this.yy={}}return n.lexer=r,i.prototype=n,n.Parser=i,new i}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(1428).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},7062:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,4],n=[1,5],r=[1,6],i=[1,7],a=[1,9],o=[1,11,13,20,21,22,23],s=[2,5],c=[1,6,11,13,20,21,22,23],u=[20,21,22],l=[2,8],h=[1,18],f=[1,19],d=[1,24],p=[6,20,21,22,23],y={trace:function(){},yy:{},symbols_:{error:2,start:3,eol:4,directive:5,PIE:6,document:7,showData:8,line:9,statement:10,txt:11,value:12,title:13,title_value:14,openDirective:15,typeDirective:16,closeDirective:17,":":18,argDirective:19,NEWLINE:20,";":21,EOF:22,open_directive:23,type_directive:24,arg_directive:25,close_directive:26,$accept:0,$end:1},terminals_:{2:"error",6:"PIE",8:"showData",11:"txt",12:"value",13:"title",14:"title_value",18:":",20:"NEWLINE",21:";",22:"EOF",23:"open_directive",24:"type_directive",25:"arg_directive",26:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,3],[7,0],[7,2],[9,2],[10,0],[10,2],[10,2],[10,1],[5,3],[5,5],[4,1],[4,1],[4,1],[15,1],[16,1],[19,1],[17,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:r.setShowData(!0);break;case 7:this.$=a[s-1];break;case 9:r.addSection(a[s-1],r.cleanupValue(a[s]));break;case 10:this.$=a[s].trim(),r.setTitle(this.$);break;case 17:r.parseDirective("%%{","open_directive");break;case 18:r.parseDirective(a[s],"type_directive");break;case 19:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 20:r.parseDirective("}%%","close_directive","pie")}},table:[{3:1,4:2,5:3,6:e,15:8,20:n,21:r,22:i,23:a},{1:[3]},{3:10,4:2,5:3,6:e,15:8,20:n,21:r,22:i,23:a},{3:11,4:2,5:3,6:e,15:8,20:n,21:r,22:i,23:a},t(o,s,{7:12,8:[1,13]}),t(c,[2,14]),t(c,[2,15]),t(c,[2,16]),{16:14,24:[1,15]},{24:[2,17]},{1:[2,1]},{1:[2,2]},t(u,l,{15:8,9:16,10:17,5:20,1:[2,3],11:h,13:f,23:a}),t(o,s,{7:21}),{17:22,18:[1,23],26:d},t([18,26],[2,18]),t(o,[2,6]),{4:25,20:n,21:r,22:i},{12:[1,26]},{14:[1,27]},t(u,[2,11]),t(u,l,{15:8,9:16,10:17,5:20,1:[2,4],11:h,13:f,23:a}),t(p,[2,12]),{19:28,25:[1,29]},t(p,[2,20]),t(o,[2,7]),t(u,[2,9]),t(u,[2,10]),{17:30,26:d},{26:[2,19]},t(p,[2,13])],defaultActions:{9:[2,17],10:[2,1],11:[2,2],29:[2,19]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=a.slice.call(arguments,1),f=Object.create(this.lexer),d={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(d.yy[p]=this.yy[p]);f.setInput(t,d.yy),d.yy.lexer=f,d.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var y=f.yylloc;a.push(y);var g=f.options&&f.options.ranges;function m(){var t;return"number"!=typeof(t=r.pop()||f.lex()||1)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof d.yy.parseError?this.parseError=d.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var v,b,_,x,w,k,T,E,C,S={};;){if(_=n[n.length-1],this.defaultActions[_]?x=this.defaultActions[_]:(null==v&&(v=m()),x=o[_]&&o[_][v]),void 0===x||!x.length||!x[0]){var A;for(k in C=[],o[_])this.terminals_[k]&&k>2&&C.push("'"+this.terminals_[k]+"'");A=f.showPosition?"Parse error on line "+(c+1)+":\n"+f.showPosition()+"\nExpecting "+C.join(", ")+", got '"+(this.terminals_[v]||v)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==v?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(A,{text:f.match,token:this.terminals_[v]||v,line:f.yylineno,loc:y,expected:C})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+v);switch(x[0]){case 1:n.push(v),i.push(f.yytext),a.push(f.yylloc),n.push(x[1]),v=null,b?(v=b,b=null):(u=f.yyleng,s=f.yytext,c=f.yylineno,y=f.yylloc,l>0&&l--);break;case 2:if(T=this.productions_[x[1]][1],S.$=i[i.length-T],S._$={first_line:a[a.length-(T||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(T||1)].first_column,last_column:a[a.length-1].last_column},g&&(S._$.range=[a[a.length-(T||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[s,u,c,d.yy,x[1],i,a].concat(h))))return w;T&&(n=n.slice(0,-1*T*2),i=i.slice(0,-1*T),a=a.slice(0,-1*T)),n.push(this.productions_[x[1]][0]),i.push(S.$),a.push(S._$),E=o[n[n.length-2]][n[n.length-1]],n.push(E);break;case 3:return!0}}return!0}},g={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),23;case 1:return this.begin("type_directive"),24;case 2:return this.popState(),this.begin("arg_directive"),18;case 3:return this.popState(),this.popState(),26;case 4:return 25;case 5:case 6:case 8:case 9:break;case 7:return 20;case 10:return this.begin("title"),13;case 11:return this.popState(),"title_value";case 12:this.begin("string");break;case 13:this.popState();break;case 14:return"txt";case 15:return 6;case 16:return 8;case 17:return"value";case 18:return 22}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n\r]+)/i,/^(?:%%[^\n]*)/i,/^(?:[\s]+)/i,/^(?:title\b)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:pie\b)/i,/^(?:showData\b)/i,/^(?::[\s]*[\d]+(?:\.[\d]+)?)/i,/^(?:$)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},title:{rules:[11],inclusive:!1},string:{rules:[13,14],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,12,15,16,17,18],inclusive:!0}}};function m(){this.yy={}}return y.lexer=g,m.prototype=y,y.Parser=m,new m}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(4551).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},3176:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,3],n=[1,5],r=[1,17],i=[2,10],a=[1,21],o=[1,22],s=[1,23],c=[1,24],u=[1,25],l=[1,26],h=[1,19],f=[1,27],d=[1,28],p=[1,31],y=[66,67],g=[5,8,14,35,36,37,38,39,40,48,55,57,66,67],m=[5,6,8,14,35,36,37,38,39,40,48,66,67],v=[1,51],b=[1,52],_=[1,53],x=[1,54],w=[1,55],k=[1,56],T=[1,57],E=[57,58],C=[1,69],S=[1,65],A=[1,66],M=[1,67],N=[1,68],D=[1,70],O=[1,74],B=[1,75],L=[1,72],I=[1,73],R=[5,8,14,35,36,37,38,39,40,48,66,67],F={trace:function(){},yy:{},symbols_:{error:2,start:3,directive:4,NEWLINE:5,RD:6,diagram:7,EOF:8,openDirective:9,typeDirective:10,closeDirective:11,":":12,argDirective:13,open_directive:14,type_directive:15,arg_directive:16,close_directive:17,requirementDef:18,elementDef:19,relationshipDef:20,requirementType:21,requirementName:22,STRUCT_START:23,requirementBody:24,ID:25,COLONSEP:26,id:27,TEXT:28,text:29,RISK:30,riskLevel:31,VERIFYMTHD:32,verifyType:33,STRUCT_STOP:34,REQUIREMENT:35,FUNCTIONAL_REQUIREMENT:36,INTERFACE_REQUIREMENT:37,PERFORMANCE_REQUIREMENT:38,PHYSICAL_REQUIREMENT:39,DESIGN_CONSTRAINT:40,LOW_RISK:41,MED_RISK:42,HIGH_RISK:43,VERIFY_ANALYSIS:44,VERIFY_DEMONSTRATION:45,VERIFY_INSPECTION:46,VERIFY_TEST:47,ELEMENT:48,elementName:49,elementBody:50,TYPE:51,type:52,DOCREF:53,ref:54,END_ARROW_L:55,relationship:56,LINE:57,END_ARROW_R:58,CONTAINS:59,COPIES:60,DERIVES:61,SATISFIES:62,VERIFIES:63,REFINES:64,TRACES:65,unqString:66,qString:67,$accept:0,$end:1},terminals_:{2:"error",5:"NEWLINE",6:"RD",8:"EOF",12:":",14:"open_directive",15:"type_directive",16:"arg_directive",17:"close_directive",23:"STRUCT_START",25:"ID",26:"COLONSEP",28:"TEXT",30:"RISK",32:"VERIFYMTHD",34:"STRUCT_STOP",35:"REQUIREMENT",36:"FUNCTIONAL_REQUIREMENT",37:"INTERFACE_REQUIREMENT",38:"PERFORMANCE_REQUIREMENT",39:"PHYSICAL_REQUIREMENT",40:"DESIGN_CONSTRAINT",41:"LOW_RISK",42:"MED_RISK",43:"HIGH_RISK",44:"VERIFY_ANALYSIS",45:"VERIFY_DEMONSTRATION",46:"VERIFY_INSPECTION",47:"VERIFY_TEST",48:"ELEMENT",51:"TYPE",53:"DOCREF",55:"END_ARROW_L",57:"LINE",58:"END_ARROW_R",59:"CONTAINS",60:"COPIES",61:"DERIVES",62:"SATISFIES",63:"VERIFIES",64:"REFINES",65:"TRACES",66:"unqString",67:"qString"},productions_:[0,[3,3],[3,2],[3,4],[4,3],[4,5],[9,1],[10,1],[13,1],[11,1],[7,0],[7,2],[7,2],[7,2],[7,2],[7,2],[18,5],[24,5],[24,5],[24,5],[24,5],[24,2],[24,1],[21,1],[21,1],[21,1],[21,1],[21,1],[21,1],[31,1],[31,1],[31,1],[33,1],[33,1],[33,1],[33,1],[19,5],[50,5],[50,5],[50,2],[50,1],[20,5],[20,5],[56,1],[56,1],[56,1],[56,1],[56,1],[56,1],[56,1],[22,1],[22,1],[27,1],[27,1],[29,1],[29,1],[49,1],[49,1],[52,1],[52,1],[54,1],[54,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 6:r.parseDirective("%%{","open_directive");break;case 7:r.parseDirective(a[s],"type_directive");break;case 8:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 9:r.parseDirective("}%%","close_directive","pie");break;case 10:this.$=[];break;case 16:r.addRequirement(a[s-3],a[s-4]);break;case 17:r.setNewReqId(a[s-2]);break;case 18:r.setNewReqText(a[s-2]);break;case 19:r.setNewReqRisk(a[s-2]);break;case 20:r.setNewReqVerifyMethod(a[s-2]);break;case 23:this.$=r.RequirementType.REQUIREMENT;break;case 24:this.$=r.RequirementType.FUNCTIONAL_REQUIREMENT;break;case 25:this.$=r.RequirementType.INTERFACE_REQUIREMENT;break;case 26:this.$=r.RequirementType.PERFORMANCE_REQUIREMENT;break;case 27:this.$=r.RequirementType.PHYSICAL_REQUIREMENT;break;case 28:this.$=r.RequirementType.DESIGN_CONSTRAINT;break;case 29:this.$=r.RiskLevel.LOW_RISK;break;case 30:this.$=r.RiskLevel.MED_RISK;break;case 31:this.$=r.RiskLevel.HIGH_RISK;break;case 32:this.$=r.VerifyType.VERIFY_ANALYSIS;break;case 33:this.$=r.VerifyType.VERIFY_DEMONSTRATION;break;case 34:this.$=r.VerifyType.VERIFY_INSPECTION;break;case 35:this.$=r.VerifyType.VERIFY_TEST;break;case 36:r.addElement(a[s-3]);break;case 37:r.setNewElementType(a[s-2]);break;case 38:r.setNewElementDocRef(a[s-2]);break;case 41:r.addRelationship(a[s-2],a[s],a[s-4]);break;case 42:r.addRelationship(a[s-2],a[s-4],a[s]);break;case 43:this.$=r.Relationships.CONTAINS;break;case 44:this.$=r.Relationships.COPIES;break;case 45:this.$=r.Relationships.DERIVES;break;case 46:this.$=r.Relationships.SATISFIES;break;case 47:this.$=r.Relationships.VERIFIES;break;case 48:this.$=r.Relationships.REFINES;break;case 49:this.$=r.Relationships.TRACES}},table:[{3:1,4:2,6:e,9:4,14:n},{1:[3]},{3:7,4:2,5:[1,6],6:e,9:4,14:n},{5:[1,8]},{10:9,15:[1,10]},{15:[2,6]},{3:11,4:2,6:e,9:4,14:n},{1:[2,2]},{4:16,5:r,7:12,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:c,39:u,40:l,48:h,66:f,67:d},{11:29,12:[1,30],17:p},t([12,17],[2,7]),{1:[2,1]},{8:[1,32]},{4:16,5:r,7:33,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:c,39:u,40:l,48:h,66:f,67:d},{4:16,5:r,7:34,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:c,39:u,40:l,48:h,66:f,67:d},{4:16,5:r,7:35,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:c,39:u,40:l,48:h,66:f,67:d},{4:16,5:r,7:36,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:c,39:u,40:l,48:h,66:f,67:d},{4:16,5:r,7:37,8:i,9:4,14:n,18:13,19:14,20:15,21:18,27:20,35:a,36:o,37:s,38:c,39:u,40:l,48:h,66:f,67:d},{22:38,66:[1,39],67:[1,40]},{49:41,66:[1,42],67:[1,43]},{55:[1,44],57:[1,45]},t(y,[2,23]),t(y,[2,24]),t(y,[2,25]),t(y,[2,26]),t(y,[2,27]),t(y,[2,28]),t(g,[2,52]),t(g,[2,53]),t(m,[2,4]),{13:46,16:[1,47]},t(m,[2,9]),{1:[2,3]},{8:[2,11]},{8:[2,12]},{8:[2,13]},{8:[2,14]},{8:[2,15]},{23:[1,48]},{23:[2,50]},{23:[2,51]},{23:[1,49]},{23:[2,56]},{23:[2,57]},{56:50,59:v,60:b,61:_,62:x,63:w,64:k,65:T},{56:58,59:v,60:b,61:_,62:x,63:w,64:k,65:T},{11:59,17:p},{17:[2,8]},{5:[1,60]},{5:[1,61]},{57:[1,62]},t(E,[2,43]),t(E,[2,44]),t(E,[2,45]),t(E,[2,46]),t(E,[2,47]),t(E,[2,48]),t(E,[2,49]),{58:[1,63]},t(m,[2,5]),{5:C,24:64,25:S,28:A,30:M,32:N,34:D},{5:O,34:B,50:71,51:L,53:I},{27:76,66:f,67:d},{27:77,66:f,67:d},t(R,[2,16]),{26:[1,78]},{26:[1,79]},{26:[1,80]},{26:[1,81]},{5:C,24:82,25:S,28:A,30:M,32:N,34:D},t(R,[2,22]),t(R,[2,36]),{26:[1,83]},{26:[1,84]},{5:O,34:B,50:85,51:L,53:I},t(R,[2,40]),t(R,[2,41]),t(R,[2,42]),{27:86,66:f,67:d},{29:87,66:[1,88],67:[1,89]},{31:90,41:[1,91],42:[1,92],43:[1,93]},{33:94,44:[1,95],45:[1,96],46:[1,97],47:[1,98]},t(R,[2,21]),{52:99,66:[1,100],67:[1,101]},{54:102,66:[1,103],67:[1,104]},t(R,[2,39]),{5:[1,105]},{5:[1,106]},{5:[2,54]},{5:[2,55]},{5:[1,107]},{5:[2,29]},{5:[2,30]},{5:[2,31]},{5:[1,108]},{5:[2,32]},{5:[2,33]},{5:[2,34]},{5:[2,35]},{5:[1,109]},{5:[2,58]},{5:[2,59]},{5:[1,110]},{5:[2,60]},{5:[2,61]},{5:C,24:111,25:S,28:A,30:M,32:N,34:D},{5:C,24:112,25:S,28:A,30:M,32:N,34:D},{5:C,24:113,25:S,28:A,30:M,32:N,34:D},{5:C,24:114,25:S,28:A,30:M,32:N,34:D},{5:O,34:B,50:115,51:L,53:I},{5:O,34:B,50:116,51:L,53:I},t(R,[2,17]),t(R,[2,18]),t(R,[2,19]),t(R,[2,20]),t(R,[2,37]),t(R,[2,38])],defaultActions:{5:[2,6],7:[2,2],11:[2,1],32:[2,3],33:[2,11],34:[2,12],35:[2,13],36:[2,14],37:[2,15],39:[2,50],40:[2,51],42:[2,56],43:[2,57],47:[2,8],88:[2,54],89:[2,55],91:[2,29],92:[2,30],93:[2,31],95:[2,32],96:[2,33],97:[2,34],98:[2,35],100:[2,58],101:[2,59],103:[2,60],104:[2,61]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=a.slice.call(arguments,1),f=Object.create(this.lexer),d={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(d.yy[p]=this.yy[p]);f.setInput(t,d.yy),d.yy.lexer=f,d.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var y=f.yylloc;a.push(y);var g=f.options&&f.options.ranges;function m(){var t;return"number"!=typeof(t=r.pop()||f.lex()||1)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof d.yy.parseError?this.parseError=d.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var v,b,_,x,w,k,T,E,C,S={};;){if(_=n[n.length-1],this.defaultActions[_]?x=this.defaultActions[_]:(null==v&&(v=m()),x=o[_]&&o[_][v]),void 0===x||!x.length||!x[0]){var A;for(k in C=[],o[_])this.terminals_[k]&&k>2&&C.push("'"+this.terminals_[k]+"'");A=f.showPosition?"Parse error on line "+(c+1)+":\n"+f.showPosition()+"\nExpecting "+C.join(", ")+", got '"+(this.terminals_[v]||v)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==v?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(A,{text:f.match,token:this.terminals_[v]||v,line:f.yylineno,loc:y,expected:C})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+v);switch(x[0]){case 1:n.push(v),i.push(f.yytext),a.push(f.yylloc),n.push(x[1]),v=null,b?(v=b,b=null):(u=f.yyleng,s=f.yytext,c=f.yylineno,y=f.yylloc,l>0&&l--);break;case 2:if(T=this.productions_[x[1]][1],S.$=i[i.length-T],S._$={first_line:a[a.length-(T||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(T||1)].first_column,last_column:a[a.length-1].last_column},g&&(S._$.range=[a[a.length-(T||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[s,u,c,d.yy,x[1],i,a].concat(h))))return w;T&&(n=n.slice(0,-1*T*2),i=i.slice(0,-1*T),a=a.slice(0,-1*T)),n.push(this.productions_[x[1]][0]),i.push(S.$),a.push(S._$),E=o[n[n.length-2]][n[n.length-1]],n.push(E);break;case 3:return!0}}return!0}},P={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),14;case 1:return this.begin("type_directive"),15;case 2:return this.popState(),this.begin("arg_directive"),12;case 3:return this.popState(),this.popState(),17;case 4:return 16;case 5:return 5;case 6:case 7:case 8:break;case 9:return 8;case 10:return 6;case 11:return 23;case 12:return 34;case 13:return 26;case 14:return 25;case 15:return 28;case 16:return 30;case 17:return 32;case 18:return 35;case 19:return 36;case 20:return 37;case 21:return 38;case 22:return 39;case 23:return 40;case 24:return 41;case 25:return 42;case 26:return 43;case 27:return 44;case 28:return 45;case 29:return 46;case 30:return 47;case 31:return 48;case 32:return 59;case 33:return 60;case 34:return 61;case 35:return 62;case 36:return 63;case 37:return 64;case 38:return 65;case 39:return 51;case 40:return 53;case 41:return 55;case 42:return 58;case 43:return 57;case 44:this.begin("string");break;case 45:this.popState();break;case 46:return"qString";case 47:return e.yytext=e.yytext.trim(),66}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:$)/i,/^(?:requirementDiagram\b)/i,/^(?:\{)/i,/^(?:\})/i,/^(?::)/i,/^(?:id\b)/i,/^(?:text\b)/i,/^(?:risk\b)/i,/^(?:verifyMethod\b)/i,/^(?:requirement\b)/i,/^(?:functionalRequirement\b)/i,/^(?:interfaceRequirement\b)/i,/^(?:performanceRequirement\b)/i,/^(?:physicalRequirement\b)/i,/^(?:designConstraint\b)/i,/^(?:low\b)/i,/^(?:medium\b)/i,/^(?:high\b)/i,/^(?:analysis\b)/i,/^(?:demonstration\b)/i,/^(?:inspection\b)/i,/^(?:test\b)/i,/^(?:element\b)/i,/^(?:contains\b)/i,/^(?:copies\b)/i,/^(?:derives\b)/i,/^(?:satisfies\b)/i,/^(?:verifies\b)/i,/^(?:refines\b)/i,/^(?:traces\b)/i,/^(?:type\b)/i,/^(?:docref\b)/i,/^(?:<-)/i,/^(?:->)/i,/^(?:-)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[\w][^\r\n\{\<\>\-\=]*)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},unqString:{rules:[],inclusive:!1},token:{rules:[],inclusive:!1},string:{rules:[45,46],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,47],inclusive:!0}}};function j(){this.yy={}}return F.lexer=P,j.prototype=F,F.Parser=j,new j}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(8800).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},6876:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,5],i=[1,7],a=[2,5],o=[1,15],s=[1,17],c=[1,18],u=[1,19],l=[1,21],h=[1,22],f=[1,23],d=[1,29],p=[1,30],y=[1,31],g=[1,32],m=[1,33],v=[1,34],b=[1,37],_=[1,38],x=[1,39],w=[1,40],k=[1,41],T=[1,42],E=[1,45],C=[1,4,5,16,20,22,23,24,30,32,33,34,35,36,38,40,41,42,46,47,48,49,57,67],S=[1,58],A=[4,5,16,20,22,23,24,30,32,33,34,35,36,38,42,46,47,48,49,57,67],M=[4,5,16,20,22,23,24,30,32,33,34,35,36,38,41,42,46,47,48,49,57,67],N=[4,5,16,20,22,23,24,30,32,33,34,35,36,38,40,42,46,47,48,49,57,67],D=[55,56,57],O=[1,4,5,7,16,20,22,23,24,30,32,33,34,35,36,38,40,41,42,46,47,48,49,57,67],B={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NEWLINE:5,directive:6,SD:7,document:8,line:9,statement:10,openDirective:11,typeDirective:12,closeDirective:13,":":14,argDirective:15,participant:16,actor:17,AS:18,restOfLine:19,participant_actor:20,signal:21,autonumber:22,activate:23,deactivate:24,note_statement:25,links_statement:26,link_statement:27,properties_statement:28,details_statement:29,title:30,text2:31,loop:32,end:33,rect:34,opt:35,alt:36,else_sections:37,par:38,par_sections:39,and:40,else:41,note:42,placement:43,over:44,actor_pair:45,links:46,link:47,properties:48,details:49,spaceList:50,",":51,left_of:52,right_of:53,signaltype:54,"+":55,"-":56,ACTOR:57,SOLID_OPEN_ARROW:58,DOTTED_OPEN_ARROW:59,SOLID_ARROW:60,DOTTED_ARROW:61,SOLID_CROSS:62,DOTTED_CROSS:63,SOLID_POINT:64,DOTTED_POINT:65,TXT:66,open_directive:67,type_directive:68,arg_directive:69,close_directive:70,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NEWLINE",7:"SD",14:":",16:"participant",18:"AS",19:"restOfLine",20:"participant_actor",22:"autonumber",23:"activate",24:"deactivate",30:"title",32:"loop",33:"end",34:"rect",35:"opt",36:"alt",38:"par",40:"and",41:"else",42:"note",44:"over",46:"links",47:"link",48:"properties",49:"details",51:",",52:"left_of",53:"right_of",55:"+",56:"-",57:"ACTOR",58:"SOLID_OPEN_ARROW",59:"DOTTED_OPEN_ARROW",60:"SOLID_ARROW",61:"DOTTED_ARROW",62:"SOLID_CROSS",63:"DOTTED_CROSS",64:"SOLID_POINT",65:"DOTTED_POINT",66:"TXT",67:"open_directive",68:"type_directive",69:"arg_directive",70:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,2],[8,0],[8,2],[9,2],[9,1],[9,1],[6,4],[6,6],[10,5],[10,3],[10,5],[10,3],[10,2],[10,1],[10,3],[10,3],[10,2],[10,2],[10,2],[10,2],[10,2],[10,3],[10,4],[10,4],[10,4],[10,4],[10,4],[10,1],[39,1],[39,4],[37,1],[37,4],[25,4],[25,4],[26,3],[27,3],[28,3],[29,3],[50,2],[50,1],[45,3],[45,1],[43,1],[43,1],[21,5],[21,5],[21,4],[17,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[31,1],[11,1],[12,1],[15,1],[13,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:return r.apply(a[s]),a[s];case 5:case 9:this.$=[];break;case 6:a[s-1].push(a[s]),this.$=a[s-1];break;case 7:case 8:case 45:this.$=a[s];break;case 12:a[s-3].type="addParticipant",a[s-3].description=r.parseMessage(a[s-1]),this.$=a[s-3];break;case 13:a[s-1].type="addParticipant",this.$=a[s-1];break;case 14:a[s-3].type="addActor",a[s-3].description=r.parseMessage(a[s-1]),this.$=a[s-3];break;case 15:a[s-1].type="addActor",this.$=a[s-1];break;case 17:r.enableSequenceNumbers();break;case 18:this.$={type:"activeStart",signalType:r.LINETYPE.ACTIVE_START,actor:a[s-1]};break;case 19:this.$={type:"activeEnd",signalType:r.LINETYPE.ACTIVE_END,actor:a[s-1]};break;case 25:this.$=[{type:"setTitle",text:a[s-1]}];break;case 26:a[s-1].unshift({type:"loopStart",loopText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.LOOP_START}),a[s-1].push({type:"loopEnd",loopText:a[s-2],signalType:r.LINETYPE.LOOP_END}),this.$=a[s-1];break;case 27:a[s-1].unshift({type:"rectStart",color:r.parseMessage(a[s-2]),signalType:r.LINETYPE.RECT_START}),a[s-1].push({type:"rectEnd",color:r.parseMessage(a[s-2]),signalType:r.LINETYPE.RECT_END}),this.$=a[s-1];break;case 28:a[s-1].unshift({type:"optStart",optText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.OPT_START}),a[s-1].push({type:"optEnd",optText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.OPT_END}),this.$=a[s-1];break;case 29:a[s-1].unshift({type:"altStart",altText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.ALT_START}),a[s-1].push({type:"altEnd",signalType:r.LINETYPE.ALT_END}),this.$=a[s-1];break;case 30:a[s-1].unshift({type:"parStart",parText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.PAR_START}),a[s-1].push({type:"parEnd",signalType:r.LINETYPE.PAR_END}),this.$=a[s-1];break;case 33:this.$=a[s-3].concat([{type:"and",parText:r.parseMessage(a[s-1]),signalType:r.LINETYPE.PAR_AND},a[s]]);break;case 35:this.$=a[s-3].concat([{type:"else",altText:r.parseMessage(a[s-1]),signalType:r.LINETYPE.ALT_ELSE},a[s]]);break;case 36:this.$=[a[s-1],{type:"addNote",placement:a[s-2],actor:a[s-1].actor,text:a[s]}];break;case 37:a[s-2]=[].concat(a[s-1],a[s-1]).slice(0,2),a[s-2][0]=a[s-2][0].actor,a[s-2][1]=a[s-2][1].actor,this.$=[a[s-1],{type:"addNote",placement:r.PLACEMENT.OVER,actor:a[s-2].slice(0,2),text:a[s]}];break;case 38:this.$=[a[s-1],{type:"addLinks",actor:a[s-1].actor,text:a[s]}];break;case 39:this.$=[a[s-1],{type:"addALink",actor:a[s-1].actor,text:a[s]}];break;case 40:this.$=[a[s-1],{type:"addProperties",actor:a[s-1].actor,text:a[s]}];break;case 41:this.$=[a[s-1],{type:"addDetails",actor:a[s-1].actor,text:a[s]}];break;case 44:this.$=[a[s-2],a[s]];break;case 46:this.$=r.PLACEMENT.LEFTOF;break;case 47:this.$=r.PLACEMENT.RIGHTOF;break;case 48:this.$=[a[s-4],a[s-1],{type:"addMessage",from:a[s-4].actor,to:a[s-1].actor,signalType:a[s-3],msg:a[s]},{type:"activeStart",signalType:r.LINETYPE.ACTIVE_START,actor:a[s-1]}];break;case 49:this.$=[a[s-4],a[s-1],{type:"addMessage",from:a[s-4].actor,to:a[s-1].actor,signalType:a[s-3],msg:a[s]},{type:"activeEnd",signalType:r.LINETYPE.ACTIVE_END,actor:a[s-4]}];break;case 50:this.$=[a[s-3],a[s-1],{type:"addMessage",from:a[s-3].actor,to:a[s-1].actor,signalType:a[s-2],msg:a[s]}];break;case 51:this.$={type:"addParticipant",actor:a[s]};break;case 52:this.$=r.LINETYPE.SOLID_OPEN;break;case 53:this.$=r.LINETYPE.DOTTED_OPEN;break;case 54:this.$=r.LINETYPE.SOLID;break;case 55:this.$=r.LINETYPE.DOTTED;break;case 56:this.$=r.LINETYPE.SOLID_CROSS;break;case 57:this.$=r.LINETYPE.DOTTED_CROSS;break;case 58:this.$=r.LINETYPE.SOLID_POINT;break;case 59:this.$=r.LINETYPE.DOTTED_POINT;break;case 60:this.$=r.parseMessage(a[s].trim().substring(1));break;case 61:r.parseDirective("%%{","open_directive");break;case 62:r.parseDirective(a[s],"type_directive");break;case 63:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 64:r.parseDirective("}%%","close_directive","sequence")}},table:[{3:1,4:e,5:n,6:4,7:r,11:6,67:i},{1:[3]},{3:8,4:e,5:n,6:4,7:r,11:6,67:i},{3:9,4:e,5:n,6:4,7:r,11:6,67:i},{3:10,4:e,5:n,6:4,7:r,11:6,67:i},t([1,4,5,16,20,22,23,24,30,32,34,35,36,38,42,46,47,48,49,57,67],a,{8:11}),{12:12,68:[1,13]},{68:[2,61]},{1:[2,1]},{1:[2,2]},{1:[2,3]},{1:[2,4],4:o,5:s,6:35,9:14,10:16,11:6,16:c,17:36,20:u,21:20,22:l,23:h,24:f,25:24,26:25,27:26,28:27,29:28,30:d,32:p,34:y,35:g,36:m,38:v,42:b,46:_,47:x,48:w,49:k,57:T,67:i},{13:43,14:[1,44],70:E},t([14,70],[2,62]),t(C,[2,6]),{6:35,10:46,11:6,16:c,17:36,20:u,21:20,22:l,23:h,24:f,25:24,26:25,27:26,28:27,29:28,30:d,32:p,34:y,35:g,36:m,38:v,42:b,46:_,47:x,48:w,49:k,57:T,67:i},t(C,[2,8]),t(C,[2,9]),{17:47,57:T},{17:48,57:T},{5:[1,49]},t(C,[2,17]),{17:50,57:T},{17:51,57:T},{5:[1,52]},{5:[1,53]},{5:[1,54]},{5:[1,55]},{5:[1,56]},{31:57,66:S},{19:[1,59]},{19:[1,60]},{19:[1,61]},{19:[1,62]},{19:[1,63]},t(C,[2,31]),{54:64,58:[1,65],59:[1,66],60:[1,67],61:[1,68],62:[1,69],63:[1,70],64:[1,71],65:[1,72]},{43:73,44:[1,74],52:[1,75],53:[1,76]},{17:77,57:T},{17:78,57:T},{17:79,57:T},{17:80,57:T},t([5,18,51,58,59,60,61,62,63,64,65,66],[2,51]),{5:[1,81]},{15:82,69:[1,83]},{5:[2,64]},t(C,[2,7]),{5:[1,85],18:[1,84]},{5:[1,87],18:[1,86]},t(C,[2,16]),{5:[1,88]},{5:[1,89]},t(C,[2,20]),t(C,[2,21]),t(C,[2,22]),t(C,[2,23]),t(C,[2,24]),{5:[1,90]},{5:[2,60]},t(A,a,{8:91}),t(A,a,{8:92}),t(A,a,{8:93}),t(M,a,{37:94,8:95}),t(N,a,{39:96,8:97}),{17:100,55:[1,98],56:[1,99],57:T},t(D,[2,52]),t(D,[2,53]),t(D,[2,54]),t(D,[2,55]),t(D,[2,56]),t(D,[2,57]),t(D,[2,58]),t(D,[2,59]),{17:101,57:T},{17:103,45:102,57:T},{57:[2,46]},{57:[2,47]},{31:104,66:S},{31:105,66:S},{31:106,66:S},{31:107,66:S},t(O,[2,10]),{13:108,70:E},{70:[2,63]},{19:[1,109]},t(C,[2,13]),{19:[1,110]},t(C,[2,15]),t(C,[2,18]),t(C,[2,19]),t(C,[2,25]),{4:o,5:s,6:35,9:14,10:16,11:6,16:c,17:36,20:u,21:20,22:l,23:h,24:f,25:24,26:25,27:26,28:27,29:28,30:d,32:p,33:[1,111],34:y,35:g,36:m,38:v,42:b,46:_,47:x,48:w,49:k,57:T,67:i},{4:o,5:s,6:35,9:14,10:16,11:6,16:c,17:36,20:u,21:20,22:l,23:h,24:f,25:24,26:25,27:26,28:27,29:28,30:d,32:p,33:[1,112],34:y,35:g,36:m,38:v,42:b,46:_,47:x,48:w,49:k,57:T,67:i},{4:o,5:s,6:35,9:14,10:16,11:6,16:c,17:36,20:u,21:20,22:l,23:h,24:f,25:24,26:25,27:26,28:27,29:28,30:d,32:p,33:[1,113],34:y,35:g,36:m,38:v,42:b,46:_,47:x,48:w,49:k,57:T,67:i},{33:[1,114]},{4:o,5:s,6:35,9:14,10:16,11:6,16:c,17:36,20:u,21:20,22:l,23:h,24:f,25:24,26:25,27:26,28:27,29:28,30:d,32:p,33:[2,34],34:y,35:g,36:m,38:v,41:[1,115],42:b,46:_,47:x,48:w,49:k,57:T,67:i},{33:[1,116]},{4:o,5:s,6:35,9:14,10:16,11:6,16:c,17:36,20:u,21:20,22:l,23:h,24:f,25:24,26:25,27:26,28:27,29:28,30:d,32:p,33:[2,32],34:y,35:g,36:m,38:v,40:[1,117],42:b,46:_,47:x,48:w,49:k,57:T,67:i},{17:118,57:T},{17:119,57:T},{31:120,66:S},{31:121,66:S},{31:122,66:S},{51:[1,123],66:[2,45]},{5:[2,38]},{5:[2,39]},{5:[2,40]},{5:[2,41]},{5:[1,124]},{5:[1,125]},{5:[1,126]},t(C,[2,26]),t(C,[2,27]),t(C,[2,28]),t(C,[2,29]),{19:[1,127]},t(C,[2,30]),{19:[1,128]},{31:129,66:S},{31:130,66:S},{5:[2,50]},{5:[2,36]},{5:[2,37]},{17:131,57:T},t(O,[2,11]),t(C,[2,12]),t(C,[2,14]),t(M,a,{8:95,37:132}),t(N,a,{8:97,39:133}),{5:[2,48]},{5:[2,49]},{66:[2,44]},{33:[2,35]},{33:[2,33]}],defaultActions:{7:[2,61],8:[2,1],9:[2,2],10:[2,3],45:[2,64],58:[2,60],75:[2,46],76:[2,47],83:[2,63],104:[2,38],105:[2,39],106:[2,40],107:[2,41],120:[2,50],121:[2,36],122:[2,37],129:[2,48],130:[2,49],131:[2,44],132:[2,35],133:[2,33]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=a.slice.call(arguments,1),f=Object.create(this.lexer),d={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(d.yy[p]=this.yy[p]);f.setInput(t,d.yy),d.yy.lexer=f,d.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var y=f.yylloc;a.push(y);var g=f.options&&f.options.ranges;function m(){var t;return"number"!=typeof(t=r.pop()||f.lex()||1)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof d.yy.parseError?this.parseError=d.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var v,b,_,x,w,k,T,E,C,S={};;){if(_=n[n.length-1],this.defaultActions[_]?x=this.defaultActions[_]:(null==v&&(v=m()),x=o[_]&&o[_][v]),void 0===x||!x.length||!x[0]){var A;for(k in C=[],o[_])this.terminals_[k]&&k>2&&C.push("'"+this.terminals_[k]+"'");A=f.showPosition?"Parse error on line "+(c+1)+":\n"+f.showPosition()+"\nExpecting "+C.join(", ")+", got '"+(this.terminals_[v]||v)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==v?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(A,{text:f.match,token:this.terminals_[v]||v,line:f.yylineno,loc:y,expected:C})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+v);switch(x[0]){case 1:n.push(v),i.push(f.yytext),a.push(f.yylloc),n.push(x[1]),v=null,b?(v=b,b=null):(u=f.yyleng,s=f.yytext,c=f.yylineno,y=f.yylloc,l>0&&l--);break;case 2:if(T=this.productions_[x[1]][1],S.$=i[i.length-T],S._$={first_line:a[a.length-(T||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(T||1)].first_column,last_column:a[a.length-1].last_column},g&&(S._$.range=[a[a.length-(T||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[s,u,c,d.yy,x[1],i,a].concat(h))))return w;T&&(n=n.slice(0,-1*T*2),i=i.slice(0,-1*T),a=a.slice(0,-1*T)),n.push(this.productions_[x[1]][0]),i.push(S.$),a.push(S._$),E=o[n[n.length-2]][n[n.length-1]],n.push(E);break;case 3:return!0}}return!0}},L={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),67;case 1:return this.begin("type_directive"),68;case 2:return this.popState(),this.begin("arg_directive"),14;case 3:return this.popState(),this.popState(),70;case 4:return 69;case 5:case 39:case 52:return 5;case 6:case 7:case 8:case 9:case 10:break;case 11:return this.begin("ID"),16;case 12:return this.begin("ID"),20;case 13:return e.yytext=e.yytext.trim(),this.begin("ALIAS"),57;case 14:return this.popState(),this.popState(),this.begin("LINE"),18;case 15:return this.popState(),this.popState(),5;case 16:return this.begin("LINE"),32;case 17:return this.begin("LINE"),34;case 18:return this.begin("LINE"),35;case 19:return this.begin("LINE"),36;case 20:return this.begin("LINE"),41;case 21:return this.begin("LINE"),38;case 22:return this.begin("LINE"),40;case 23:return this.popState(),19;case 24:return 33;case 25:return 52;case 26:return 53;case 27:return 46;case 28:return 47;case 29:return 48;case 30:return 49;case 31:return 44;case 32:return 42;case 33:return this.begin("ID"),23;case 34:return this.begin("ID"),24;case 35:return 30;case 36:return 7;case 37:return 22;case 38:return 51;case 40:return e.yytext=e.yytext.trim(),57;case 41:return 60;case 42:return 61;case 43:return 58;case 44:return 59;case 45:return 62;case 46:return 63;case 47:return 64;case 48:return 65;case 49:return 66;case 50:return 55;case 51:return 56;case 53:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:participant\b)/i,/^(?:actor\b)/i,/^(?:[^\->:\n,;]+?(?=((?!\n)\s)+as(?!\n)\s|[#\n;]|$))/i,/^(?:as\b)/i,/^(?:(?:))/i,/^(?:loop\b)/i,/^(?:rect\b)/i,/^(?:opt\b)/i,/^(?:alt\b)/i,/^(?:else\b)/i,/^(?:par\b)/i,/^(?:and\b)/i,/^(?:(?:[:]?(?:no)?wrap)?[^#\n;]*)/i,/^(?:end\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:links\b)/i,/^(?:link\b)/i,/^(?:properties\b)/i,/^(?:details\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:activate\b)/i,/^(?:deactivate\b)/i,/^(?:title\b)/i,/^(?:sequenceDiagram\b)/i,/^(?:autonumber\b)/i,/^(?:,)/i,/^(?:;)/i,/^(?:[^\+\->:\n,;]+((?!(-x|--x|-\)|--\)))[\-]*[^\+\->:\n,;]+)*)/i,/^(?:->>)/i,/^(?:-->>)/i,/^(?:->)/i,/^(?:-->)/i,/^(?:-[x])/i,/^(?:--[x])/i,/^(?:-[\)])/i,/^(?:--[\)])/i,/^(?::(?:(?:no)?wrap)?[^#\n;]+)/i,/^(?:\+)/i,/^(?:-)/i,/^(?:$)/i,/^(?:.)/i],conditions:{open_directive:{rules:[1,8],inclusive:!1},type_directive:{rules:[2,3,8],inclusive:!1},arg_directive:{rules:[3,4,8],inclusive:!1},ID:{rules:[7,8,13],inclusive:!1},ALIAS:{rules:[7,8,14,15],inclusive:!1},LINE:{rules:[7,8,23],inclusive:!1},INITIAL:{rules:[0,5,6,8,9,10,11,12,16,17,18,19,20,21,22,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53],inclusive:!0}}};function I(){this.yy={}}return B.lexer=L,I.prototype=B,B.Parser=I,new I}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(1993).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},3584:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,5],i=[1,7],a=[2,5],o=[1,15],s=[1,17],c=[1,19],u=[1,20],l=[1,21],h=[1,22],f=[1,30],d=[1,23],p=[1,24],y=[1,25],g=[1,26],m=[1,27],v=[1,32],b=[1,33],_=[1,34],x=[1,35],w=[1,31],k=[1,38],T=[1,4,5,14,15,17,19,20,22,23,24,25,26,27,36,37,38,39,42,45],E=[1,4,5,12,13,14,15,17,19,20,22,23,24,25,26,27,36,37,38,39,42,45],C=[1,4,5,7,14,15,17,19,20,22,23,24,25,26,27,36,37,38,39,42,45],S=[4,5,14,15,17,19,20,22,23,24,25,26,27,36,37,38,39,42,45],A={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NL:5,directive:6,SD:7,document:8,line:9,statement:10,idStatement:11,DESCR:12,"--\x3e":13,HIDE_EMPTY:14,scale:15,WIDTH:16,COMPOSIT_STATE:17,STRUCT_START:18,STRUCT_STOP:19,STATE_DESCR:20,AS:21,ID:22,FORK:23,JOIN:24,CHOICE:25,CONCURRENT:26,note:27,notePosition:28,NOTE_TEXT:29,direction:30,openDirective:31,typeDirective:32,closeDirective:33,":":34,argDirective:35,direction_tb:36,direction_bt:37,direction_rl:38,direction_lr:39,eol:40,";":41,EDGE_STATE:42,left_of:43,right_of:44,open_directive:45,type_directive:46,arg_directive:47,close_directive:48,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NL",7:"SD",12:"DESCR",13:"--\x3e",14:"HIDE_EMPTY",15:"scale",16:"WIDTH",17:"COMPOSIT_STATE",18:"STRUCT_START",19:"STRUCT_STOP",20:"STATE_DESCR",21:"AS",22:"ID",23:"FORK",24:"JOIN",25:"CHOICE",26:"CONCURRENT",27:"note",29:"NOTE_TEXT",34:":",36:"direction_tb",37:"direction_bt",38:"direction_rl",39:"direction_lr",41:";",42:"EDGE_STATE",43:"left_of",44:"right_of",45:"open_directive",46:"type_directive",47:"arg_directive",48:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,2],[8,0],[8,2],[9,2],[9,1],[9,1],[10,1],[10,2],[10,3],[10,4],[10,1],[10,2],[10,1],[10,4],[10,3],[10,6],[10,1],[10,1],[10,1],[10,1],[10,4],[10,4],[10,1],[10,1],[6,3],[6,5],[30,1],[30,1],[30,1],[30,1],[40,1],[40,1],[11,1],[11,1],[28,1],[28,1],[31,1],[32,1],[35,1],[33,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:return r.setRootDoc(a[s]),a[s];case 5:this.$=[];break;case 6:"nl"!=a[s]&&(a[s-1].push(a[s]),this.$=a[s-1]);break;case 7:case 8:case 36:case 37:this.$=a[s];break;case 9:this.$="nl";break;case 10:this.$={stmt:"state",id:a[s],type:"default",description:""};break;case 11:this.$={stmt:"state",id:a[s-1],type:"default",description:r.trimColon(a[s])};break;case 12:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-2],type:"default",description:""},state2:{stmt:"state",id:a[s],type:"default",description:""}};break;case 13:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-3],type:"default",description:""},state2:{stmt:"state",id:a[s-1],type:"default",description:""},description:a[s].substr(1).trim()};break;case 17:this.$={stmt:"state",id:a[s-3],type:"default",description:"",doc:a[s-1]};break;case 18:var c=a[s],u=a[s-2].trim();if(a[s].match(":")){var l=a[s].split(":");c=l[0],u=[u,l[1]]}this.$={stmt:"state",id:c,type:"default",description:u};break;case 19:this.$={stmt:"state",id:a[s-3],type:"default",description:a[s-5],doc:a[s-1]};break;case 20:this.$={stmt:"state",id:a[s],type:"fork"};break;case 21:this.$={stmt:"state",id:a[s],type:"join"};break;case 22:this.$={stmt:"state",id:a[s],type:"choice"};break;case 23:this.$={stmt:"state",id:r.getDividerId(),type:"divider"};break;case 24:this.$={stmt:"state",id:a[s-1].trim(),note:{position:a[s-2].trim(),text:a[s].trim()}};break;case 30:r.setDirection("TB"),this.$={stmt:"dir",value:"TB"};break;case 31:r.setDirection("BT"),this.$={stmt:"dir",value:"BT"};break;case 32:r.setDirection("RL"),this.$={stmt:"dir",value:"RL"};break;case 33:r.setDirection("LR"),this.$={stmt:"dir",value:"LR"};break;case 40:r.parseDirective("%%{","open_directive");break;case 41:r.parseDirective(a[s],"type_directive");break;case 42:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 43:r.parseDirective("}%%","close_directive","state")}},table:[{3:1,4:e,5:n,6:4,7:r,31:6,45:i},{1:[3]},{3:8,4:e,5:n,6:4,7:r,31:6,45:i},{3:9,4:e,5:n,6:4,7:r,31:6,45:i},{3:10,4:e,5:n,6:4,7:r,31:6,45:i},t([1,4,5,14,15,17,20,22,23,24,25,26,27,36,37,38,39,42,45],a,{8:11}),{32:12,46:[1,13]},{46:[2,40]},{1:[2,1]},{1:[2,2]},{1:[2,3]},{1:[2,4],4:o,5:s,6:28,9:14,10:16,11:18,14:c,15:u,17:l,20:h,22:f,23:d,24:p,25:y,26:g,27:m,30:29,31:6,36:v,37:b,38:_,39:x,42:w,45:i},{33:36,34:[1,37],48:k},t([34,48],[2,41]),t(T,[2,6]),{6:28,10:39,11:18,14:c,15:u,17:l,20:h,22:f,23:d,24:p,25:y,26:g,27:m,30:29,31:6,36:v,37:b,38:_,39:x,42:w,45:i},t(T,[2,8]),t(T,[2,9]),t(T,[2,10],{12:[1,40],13:[1,41]}),t(T,[2,14]),{16:[1,42]},t(T,[2,16],{18:[1,43]}),{21:[1,44]},t(T,[2,20]),t(T,[2,21]),t(T,[2,22]),t(T,[2,23]),{28:45,29:[1,46],43:[1,47],44:[1,48]},t(T,[2,26]),t(T,[2,27]),t(E,[2,36]),t(E,[2,37]),t(T,[2,30]),t(T,[2,31]),t(T,[2,32]),t(T,[2,33]),t(C,[2,28]),{35:49,47:[1,50]},t(C,[2,43]),t(T,[2,7]),t(T,[2,11]),{11:51,22:f,42:w},t(T,[2,15]),t(S,a,{8:52}),{22:[1,53]},{22:[1,54]},{21:[1,55]},{22:[2,38]},{22:[2,39]},{33:56,48:k},{48:[2,42]},t(T,[2,12],{12:[1,57]}),{4:o,5:s,6:28,9:14,10:16,11:18,14:c,15:u,17:l,19:[1,58],20:h,22:f,23:d,24:p,25:y,26:g,27:m,30:29,31:6,36:v,37:b,38:_,39:x,42:w,45:i},t(T,[2,18],{18:[1,59]}),{29:[1,60]},{22:[1,61]},t(C,[2,29]),t(T,[2,13]),t(T,[2,17]),t(S,a,{8:62}),t(T,[2,24]),t(T,[2,25]),{4:o,5:s,6:28,9:14,10:16,11:18,14:c,15:u,17:l,19:[1,63],20:h,22:f,23:d,24:p,25:y,26:g,27:m,30:29,31:6,36:v,37:b,38:_,39:x,42:w,45:i},t(T,[2,19])],defaultActions:{7:[2,40],8:[2,1],9:[2,2],10:[2,3],47:[2,38],48:[2,39],50:[2,42]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=a.slice.call(arguments,1),f=Object.create(this.lexer),d={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(d.yy[p]=this.yy[p]);f.setInput(t,d.yy),d.yy.lexer=f,d.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var y=f.yylloc;a.push(y);var g=f.options&&f.options.ranges;function m(){var t;return"number"!=typeof(t=r.pop()||f.lex()||1)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof d.yy.parseError?this.parseError=d.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var v,b,_,x,w,k,T,E,C,S={};;){if(_=n[n.length-1],this.defaultActions[_]?x=this.defaultActions[_]:(null==v&&(v=m()),x=o[_]&&o[_][v]),void 0===x||!x.length||!x[0]){var A;for(k in C=[],o[_])this.terminals_[k]&&k>2&&C.push("'"+this.terminals_[k]+"'");A=f.showPosition?"Parse error on line "+(c+1)+":\n"+f.showPosition()+"\nExpecting "+C.join(", ")+", got '"+(this.terminals_[v]||v)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==v?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(A,{text:f.match,token:this.terminals_[v]||v,line:f.yylineno,loc:y,expected:C})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+v);switch(x[0]){case 1:n.push(v),i.push(f.yytext),a.push(f.yylloc),n.push(x[1]),v=null,b?(v=b,b=null):(u=f.yyleng,s=f.yytext,c=f.yylineno,y=f.yylloc,l>0&&l--);break;case 2:if(T=this.productions_[x[1]][1],S.$=i[i.length-T],S._$={first_line:a[a.length-(T||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(T||1)].first_column,last_column:a[a.length-1].last_column},g&&(S._$.range=[a[a.length-(T||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[s,u,c,d.yy,x[1],i,a].concat(h))))return w;T&&(n=n.slice(0,-1*T*2),i=i.slice(0,-1*T),a=a.slice(0,-1*T)),n.push(this.productions_[x[1]][0]),i.push(S.$),a.push(S._$),E=o[n[n.length-2]][n[n.length-1]],n.push(E);break;case 3:return!0}}return!0}},M={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:case 26:return 36;case 1:case 27:return 37;case 2:case 28:return 38;case 3:case 29:return 39;case 4:return this.begin("open_directive"),45;case 5:return this.begin("type_directive"),46;case 6:return this.popState(),this.begin("arg_directive"),34;case 7:return this.popState(),this.popState(),48;case 8:return 47;case 9:case 10:case 12:case 13:case 14:case 15:case 39:case 45:break;case 11:case 59:return 5;case 16:return this.pushState("SCALE"),15;case 17:return 16;case 18:case 33:case 36:this.popState();break;case 19:this.pushState("STATE");break;case 20:case 23:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 21:case 24:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),24;case 22:case 25:return this.popState(),e.yytext=e.yytext.slice(0,-10).trim(),25;case 30:this.begin("STATE_STRING");break;case 31:return this.popState(),this.pushState("STATE_ID"),"AS";case 32:case 47:return this.popState(),"ID";case 34:return"STATE_DESCR";case 35:return 17;case 37:return this.popState(),this.pushState("struct"),18;case 38:return this.popState(),19;case 40:return this.begin("NOTE"),27;case 41:return this.popState(),this.pushState("NOTE_ID"),43;case 42:return this.popState(),this.pushState("NOTE_ID"),44;case 43:this.popState(),this.pushState("FLOATING_NOTE");break;case 44:return this.popState(),this.pushState("FLOATING_NOTE_ID"),"AS";case 46:return"NOTE_TEXT";case 48:return this.popState(),this.pushState("NOTE_TEXT"),22;case 49:return this.popState(),e.yytext=e.yytext.substr(2).trim(),29;case 50:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),29;case 51:case 52:return 7;case 53:return 14;case 54:return 42;case 55:return 22;case 56:return e.yytext=e.yytext.trim(),12;case 57:return 13;case 58:return 26;case 60:return"INVALID"}},rules:[/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:[\s]+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:state\s+)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*\[\[fork\]\])/i,/^(?:.*\[\[join\]\])/i,/^(?:.*\[\[choice\]\])/i,/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:["])/i,/^(?:\s*as\s+)/i,/^(?:[^\n\{]*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n\s\{]+)/i,/^(?:\n)/i,/^(?:\{)/i,/^(?:\})/i,/^(?:[\n])/i,/^(?:note\s+)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:")/i,/^(?:\s*as\s*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n]*)/i,/^(?:\s*[^:\n\s\-]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:[\s\S]*?end note\b)/i,/^(?:stateDiagram\s+)/i,/^(?:stateDiagram-v2\s+)/i,/^(?:hide empty description\b)/i,/^(?:\[\*\])/i,/^(?:[^:\n\s\-\{]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:-->)/i,/^(?:--)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[13,14],inclusive:!1},close_directive:{rules:[13,14],inclusive:!1},arg_directive:{rules:[7,8,13,14],inclusive:!1},type_directive:{rules:[6,7,13,14],inclusive:!1},open_directive:{rules:[5,13,14],inclusive:!1},struct:{rules:[13,14,19,26,27,28,29,38,39,40,54,55,56,57,58],inclusive:!1},FLOATING_NOTE_ID:{rules:[47],inclusive:!1},FLOATING_NOTE:{rules:[44,45,46],inclusive:!1},NOTE_TEXT:{rules:[49,50],inclusive:!1},NOTE_ID:{rules:[48],inclusive:!1},NOTE:{rules:[41,42,43],inclusive:!1},SCALE:{rules:[17,18],inclusive:!1},ALIAS:{rules:[],inclusive:!1},STATE_ID:{rules:[32],inclusive:!1},STATE_STRING:{rules:[33,34],inclusive:!1},FORK_STATE:{rules:[],inclusive:!1},STATE:{rules:[13,14,20,21,22,23,24,25,30,31,35,36,37],inclusive:!1},ID:{rules:[13,14],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,9,10,11,12,14,15,16,19,37,40,51,52,53,54,55,56,57,59,60],inclusive:!0}}};function N(){this.yy={}}return A.lexer=M,N.prototype=A,A.Parser=N,new N}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(3069).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},9763:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,17,18,19,21],i=[1,15],a=[1,16],o=[1,17],s=[1,21],c=[4,6,9,11,17,18,19,21],u={trace:function(){},yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,title:17,section:18,taskName:19,taskData:20,open_directive:21,type_directive:22,arg_directive:23,close_directive:24,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",17:"title",18:"section",19:"taskName",20:"taskData",21:"open_directive",22:"type_directive",23:"arg_directive",24:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,1],[10,2],[10,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:return a[s-1];case 3:case 7:case 8:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 11:r.setTitle(a[s].substr(6)),this.$=a[s].substr(6);break;case 12:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 13:r.addTask(a[s-1],a[s]),this.$="task";break;case 15:r.parseDirective("%%{","open_directive");break;case 16:r.parseDirective(a[s],"type_directive");break;case 17:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 18:r.parseDirective("}%%","close_directive","journey")}},table:[{3:1,4:e,7:3,12:4,21:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,21:n},{13:8,22:[1,9]},{22:[2,15]},{6:[1,10],7:18,8:11,9:[1,12],10:13,11:[1,14],12:4,17:i,18:a,19:o,21:n},{1:[2,2]},{14:19,15:[1,20],24:s},t([15,24],[2,16]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:18,10:22,12:4,17:i,18:a,19:o,21:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),t(r,[2,12]),{20:[1,23]},t(r,[2,14]),{11:[1,24]},{16:25,23:[1,26]},{11:[2,18]},t(r,[2,5]),t(r,[2,13]),t(c,[2,9]),{14:27,24:s},{24:[2,17]},{11:[1,28]},t(c,[2,10])],defaultActions:{5:[2,15],7:[2,2],21:[2,18],26:[2,17]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=a.slice.call(arguments,1),f=Object.create(this.lexer),d={yy:{}};for(var p in this.yy)Object.prototype.hasOwnProperty.call(this.yy,p)&&(d.yy[p]=this.yy[p]);f.setInput(t,d.yy),d.yy.lexer=f,d.yy.parser=this,void 0===f.yylloc&&(f.yylloc={});var y=f.yylloc;a.push(y);var g=f.options&&f.options.ranges;function m(){var t;return"number"!=typeof(t=r.pop()||f.lex()||1)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof d.yy.parseError?this.parseError=d.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var v,b,_,x,w,k,T,E,C,S={};;){if(_=n[n.length-1],this.defaultActions[_]?x=this.defaultActions[_]:(null==v&&(v=m()),x=o[_]&&o[_][v]),void 0===x||!x.length||!x[0]){var A;for(k in C=[],o[_])this.terminals_[k]&&k>2&&C.push("'"+this.terminals_[k]+"'");A=f.showPosition?"Parse error on line "+(c+1)+":\n"+f.showPosition()+"\nExpecting "+C.join(", ")+", got '"+(this.terminals_[v]||v)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==v?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(A,{text:f.match,token:this.terminals_[v]||v,line:f.yylineno,loc:y,expected:C})}if(x[0]instanceof Array&&x.length>1)throw new Error("Parse Error: multiple actions possible at state: "+_+", token: "+v);switch(x[0]){case 1:n.push(v),i.push(f.yytext),a.push(f.yylloc),n.push(x[1]),v=null,b?(v=b,b=null):(u=f.yyleng,s=f.yytext,c=f.yylineno,y=f.yylloc,l>0&&l--);break;case 2:if(T=this.productions_[x[1]][1],S.$=i[i.length-T],S._$={first_line:a[a.length-(T||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(T||1)].first_column,last_column:a[a.length-1].last_column},g&&(S._$.range=[a[a.length-(T||1)].range[0],a[a.length-1].range[1]]),void 0!==(w=this.performAction.apply(S,[s,u,c,d.yy,x[1],i,a].concat(h))))return w;T&&(n=n.slice(0,-1*T*2),i=i.slice(0,-1*T),a=a.slice(0,-1*T)),n.push(this.productions_[x[1]][0]),i.push(S.$),a.push(S._$),E=o[n[n.length-2]][n[n.length-1]],n.push(E);break;case 3:return!0}}return!0}},l={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),21;case 1:return this.begin("type_directive"),22;case 2:return this.popState(),this.begin("arg_directive"),15;case 3:return this.popState(),this.popState(),24;case 4:return 23;case 5:case 6:case 8:case 9:break;case 7:return 11;case 10:return 4;case 11:return 17;case 12:return 18;case 13:return 19;case 14:return 20;case 15:return 15;case 16:return 6;case 17:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:journey\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{open_directive:{rules:[1],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,13,14,15,16,17],inclusive:!0}}};function h(){this.yy={}}return u.lexer=l,h.prototype=u,u.Parser=h,new h}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(9143).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},9609:t=>{var e=/^(%20|\s)*(javascript|data)/im,n=/[^\x20-\x7E]/gim,r=/^([^:]+):/gm,i=[".","/"];t.exports={sanitizeUrl:function(t){if(!t)return"about:blank";var a,o,s=t.replace(n,"").trim();return function(t){return i.indexOf(t[0])>-1}(s)?s:(o=s.match(r))?(a=o[0],e.test(a)?"about:blank":s):"about:blank"}}},3841:t=>{t.exports=function(t,e){return t.intersect(e)}},7458:(t,e,n)=>{n.d(e,{Z:()=>YT});var r=n(1941),i=n.n(r),a={debug:1,info:2,warn:3,error:4,fatal:5},o={debug:function(){},info:function(){},warn:function(){},error:function(){},fatal:function(){}},s=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"fatal";isNaN(t)&&(t=t.toLowerCase(),void 0!==a[t]&&(t=a[t])),o.trace=function(){},o.debug=function(){},o.info=function(){},o.warn=function(){},o.error=function(){},o.fatal=function(){},t<=a.fatal&&(o.fatal=console.error?console.error.bind(console,c("FATAL"),"color: orange"):console.log.bind(console,"\x1b[35m",c("FATAL"))),t<=a.error&&(o.error=console.error?console.error.bind(console,c("ERROR"),"color: orange"):console.log.bind(console,"\x1b[31m",c("ERROR"))),t<=a.warn&&(o.warn=console.warn?console.warn.bind(console,c("WARN"),"color: orange"):console.log.bind(console,"\x1b[33m",c("WARN"))),t<=a.info&&(o.info=console.info?console.info.bind(console,c("INFO"),"color: lightblue"):console.log.bind(console,"\x1b[34m",c("INFO"))),t<=a.debug&&(o.debug=console.debug?console.debug.bind(console,c("DEBUG"),"color: lightgreen"):console.log.bind(console,"\x1b[32m",c("DEBUG")))},c=function(t){var e=i()().format("ss.SSS");return"%c".concat(e," : ").concat(t," : ")};function u(t,e){let n;if(void 0===e)for(const r of t)null!=r&&(n=r)&&(n=r);else{let r=-1;for(let i of t)null!=(i=e(i,++r,t))&&(n=i)&&(n=i)}return n}function l(t,e){let n;if(void 0===e)for(const r of t)null!=r&&(n>r||void 0===n&&r>=r)&&(n=r);else{let r=-1;for(let i of t)null!=(i=e(i,++r,t))&&(n>i||void 0===n&&i>=i)&&(n=i)}return n}function h(t){return t}var f=1e-6;function d(t){return"translate("+t+",0)"}function p(t){return"translate(0,"+t+")"}function y(t){return e=>+t(e)}function g(t,e){return e=Math.max(0,t.bandwidth()-2*e)/2,t.round()&&(e=Math.round(e)),n=>+t(n)+e}function m(){return!this.__axis}function v(t,e){var n=[],r=null,i=null,a=6,o=6,s=3,c="undefined"!=typeof window&&window.devicePixelRatio>1?0:.5,u=1===t||4===t?-1:1,l=4===t||2===t?"x":"y",v=1===t||3===t?d:p;function b(d){var p=null==r?e.ticks?e.ticks.apply(e,n):e.domain():r,b=null==i?e.tickFormat?e.tickFormat.apply(e,n):h:i,_=Math.max(a,0)+s,x=e.range(),w=+x[0]+c,k=+x[x.length-1]+c,T=(e.bandwidth?g:y)(e.copy(),c),E=d.selection?d.selection():d,C=E.selectAll(".domain").data([null]),S=E.selectAll(".tick").data(p,e).order(),A=S.exit(),M=S.enter().append("g").attr("class","tick"),N=S.select("line"),D=S.select("text");C=C.merge(C.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),S=S.merge(M),N=N.merge(M.append("line").attr("stroke","currentColor").attr(l+"2",u*a)),D=D.merge(M.append("text").attr("fill","currentColor").attr(l,u*_).attr("dy",1===t?"0em":3===t?"0.71em":"0.32em")),d!==E&&(C=C.transition(d),S=S.transition(d),N=N.transition(d),D=D.transition(d),A=A.transition(d).attr("opacity",f).attr("transform",(function(t){return isFinite(t=T(t))?v(t+c):this.getAttribute("transform")})),M.attr("opacity",f).attr("transform",(function(t){var e=this.parentNode.__axis;return v((e&&isFinite(e=e(t))?e:T(t))+c)}))),A.remove(),C.attr("d",4===t||2===t?o?"M"+u*o+","+w+"H"+c+"V"+k+"H"+u*o:"M"+c+","+w+"V"+k:o?"M"+w+","+u*o+"V"+c+"H"+k+"V"+u*o:"M"+w+","+c+"H"+k),S.attr("opacity",1).attr("transform",(function(t){return v(T(t)+c)})),N.attr(l+"2",u*a),D.attr(l,u*_).text(b),E.filter(m).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",2===t?"start":4===t?"end":"middle"),E.each((function(){this.__axis=T}))}return b.scale=function(t){return arguments.length?(e=t,b):e},b.ticks=function(){return n=Array.from(arguments),b},b.tickArguments=function(t){return arguments.length?(n=null==t?[]:Array.from(t),b):n.slice()},b.tickValues=function(t){return arguments.length?(r=null==t?null:Array.from(t),b):r&&r.slice()},b.tickFormat=function(t){return arguments.length?(i=t,b):i},b.tickSize=function(t){return arguments.length?(a=o=+t,b):a},b.tickSizeInner=function(t){return arguments.length?(a=+t,b):a},b.tickSizeOuter=function(t){return arguments.length?(o=+t,b):o},b.tickPadding=function(t){return arguments.length?(s=+t,b):s},b.offset=function(t){return arguments.length?(c=+t,b):c},b}function b(){}function _(t){return null==t?b:function(){return this.querySelector(t)}}function x(){return[]}function w(t){return null==t?x:function(){return this.querySelectorAll(t)}}function k(t){return function(){return this.matches(t)}}function T(t){return function(e){return e.matches(t)}}var E=Array.prototype.find;function C(){return this.firstElementChild}var S=Array.prototype.filter;function A(){return Array.from(this.children)}function M(t){return new Array(t.length)}function N(t,e){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=e}function D(t,e,n,r,i,a){for(var o,s=0,c=e.length,u=a.length;se?1:t>=e?0:NaN}N.prototype={constructor:N,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,e){return this._parent.insertBefore(t,e)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};var R="http://www.w3.org/1999/xhtml";const F={svg:"http://www.w3.org/2000/svg",xhtml:R,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};function P(t){var e=t+="",n=e.indexOf(":");return n>=0&&"xmlns"!==(e=t.slice(0,n))&&(t=t.slice(n+1)),F.hasOwnProperty(e)?{space:F[e],local:t}:t}function j(t){return function(){this.removeAttribute(t)}}function Y(t){return function(){this.removeAttributeNS(t.space,t.local)}}function z(t,e){return function(){this.setAttribute(t,e)}}function U(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function q(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttribute(t):this.setAttribute(t,n)}}function H(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,n)}}function $(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function W(t){return function(){this.style.removeProperty(t)}}function V(t,e,n){return function(){this.style.setProperty(t,e,n)}}function G(t,e,n){return function(){var r=e.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,n)}}function X(t,e){return t.style.getPropertyValue(e)||$(t).getComputedStyle(t,null).getPropertyValue(e)}function Z(t){return function(){delete this[t]}}function K(t,e){return function(){this[t]=e}}function Q(t,e){return function(){var n=e.apply(this,arguments);null==n?delete this[t]:this[t]=n}}function J(t){return t.trim().split(/^|\s+/)}function tt(t){return t.classList||new et(t)}function et(t){this._node=t,this._names=J(t.getAttribute("class")||"")}function nt(t,e){for(var n=tt(t),r=-1,i=e.length;++r=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var St=[null];function At(t,e){this._groups=t,this._parents=e}function Mt(){return new At([[document.documentElement]],St)}At.prototype=Mt.prototype={constructor:At,select:function(t){"function"!=typeof t&&(t=_(t));for(var e=this._groups,n=e.length,r=new Array(n),i=0;i=_&&(_=b+1);!(v=g[_])&&++_=0;)(r=i[a])&&(o&&4^r.compareDocumentPosition(o)&&o.parentNode.insertBefore(r,o),o=r);return this},sort:function(t){function e(e,n){return e&&n?t(e.__data__,n.__data__):!e-!n}t||(t=I);for(var n=this._groups,r=n.length,i=new Array(r),a=0;a1?this.each((null==e?W:"function"==typeof e?G:V)(t,e,null==n?"":n)):X(this.node(),t)},property:function(t,e){return arguments.length>1?this.each((null==e?Z:"function"==typeof e?Q:K)(t,e)):this.node()[t]},classed:function(t,e){var n=J(t+"");if(arguments.length<2){for(var r=tt(this.node()),i=-1,a=n.length;++i=0&&(e=t.slice(n+1),t=t.slice(0,n)),{type:t,name:e}}))}(t+""),o=a.length;if(!(arguments.length<2)){for(s=e?kt:wt,r=0;r{}};function Ot(){for(var t,e=0,n=arguments.length,r={};e=0&&(n=t.slice(r+1),t=t.slice(0,r)),t&&!e.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))}(t+"",r),a=-1,o=i.length;if(!(arguments.length<2)){if(null!=e&&"function"!=typeof e)throw new Error("invalid callback: "+e);for(;++a0)for(var n,r,i=new Array(n),a=0;a=0&&e._call.call(void 0,t),e=e._next;--jt}()}finally{jt=0,function(){for(var t,e,n=Ft,r=1/0;n;)n._call?(r>n._time&&(r=n._time),t=n,n=n._next):(e=n._next,n._next=null,n=t?t._next=e:Ft=e);Pt=t,Jt(r)}(),qt=0}}function Qt(){var t=$t.now(),e=t-Ut;e>1e3&&(Ht-=e,Ut=t)}function Jt(t){jt||(Yt&&(Yt=clearTimeout(Yt)),t-qt>24?(t<1/0&&(Yt=setTimeout(Kt,t-$t.now()-Ht)),zt&&(zt=clearInterval(zt))):(zt||(Ut=$t.now(),zt=setInterval(Qt,1e3)),jt=1,Wt(Kt)))}function te(t,e,n){var r=new Xt;return e=null==e?0:+e,r.restart((n=>{r.stop(),t(n+e)}),e,n),r}Xt.prototype=Zt.prototype={constructor:Xt,restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?Vt():+n)+(null==e?0:+e),this._next||Pt===this||(Pt?Pt._next=this:Ft=this,Pt=this),this._call=t,this._time=n,Jt()},stop:function(){this._call&&(this._call=null,this._time=1/0,Jt())}};var ee=Rt("start","end","cancel","interrupt"),ne=[];function re(t,e,n,r,i,a){var o=t.__transition;if(o){if(n in o)return}else t.__transition={};!function(t,e,n){var r,i=t.__transition;function a(c){var u,l,h,f;if(1!==n.state)return s();for(u in i)if((f=i[u]).name===n.name){if(3===f.state)return te(a);4===f.state?(f.state=6,f.timer.stop(),f.on.call("interrupt",t,t.__data__,f.index,f.group),delete i[u]):+u0)throw new Error("too late; already scheduled");return n}function ae(t,e){var n=oe(t,e);if(n.state>3)throw new Error("too late; already running");return n}function oe(t,e){var n=t.__transition;if(!n||!(n=n[e]))throw new Error("transition not found");return n}function se(t,e){return t=+t,e=+e,function(n){return t*(1-n)+e*n}}var ce,ue=180/Math.PI,le={translateX:0,translateY:0,rotate:0,skewX:0,scaleX:1,scaleY:1};function he(t,e,n,r,i,a){var o,s,c;return(o=Math.sqrt(t*t+e*e))&&(t/=o,e/=o),(c=t*n+e*r)&&(n-=t*c,r-=e*c),(s=Math.sqrt(n*n+r*r))&&(n/=s,r/=s,c/=s),t*r180?e+=360:e-t>180&&(t+=360),a.push({i:n.push(i(n)+"rotate(",null,r)-2,x:se(t,e)})):e&&n.push(i(n)+"rotate("+e+r)}(a.rotate,o.rotate,s,c),function(t,e,n,a){t!==e?a.push({i:n.push(i(n)+"skewX(",null,r)-2,x:se(t,e)}):e&&n.push(i(n)+"skewX("+e+r)}(a.skewX,o.skewX,s,c),function(t,e,n,r,a,o){if(t!==n||e!==r){var s=a.push(i(a)+"scale(",null,",",null,")");o.push({i:s-4,x:se(t,n)},{i:s-2,x:se(e,r)})}else 1===n&&1===r||a.push(i(a)+"scale("+n+","+r+")")}(a.scaleX,a.scaleY,o.scaleX,o.scaleY,s,c),a=o=null,function(t){for(var e,n=-1,r=c.length;++n>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===n?Pe(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===n?Pe(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=Se.exec(t))?new Ye(e[1],e[2],e[3],1):(e=Ae.exec(t))?new Ye(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=Me.exec(t))?Pe(e[1],e[2],e[3],e[4]):(e=Ne.exec(t))?Pe(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=De.exec(t))?He(e[1],e[2]/100,e[3]/100,1):(e=Oe.exec(t))?He(e[1],e[2]/100,e[3]/100,e[4]):Be.hasOwnProperty(t)?Fe(Be[t]):"transparent"===t?new Ye(NaN,NaN,NaN,0):null}function Fe(t){return new Ye(t>>16&255,t>>8&255,255&t,1)}function Pe(t,e,n,r){return r<=0&&(t=e=n=NaN),new Ye(t,e,n,r)}function je(t,e,n,r){return 1===arguments.length?function(t){return t instanceof _e||(t=Re(t)),t?new Ye((t=t.rgb()).r,t.g,t.b,t.opacity):new Ye}(t):new Ye(t,e,n,null==r?1:r)}function Ye(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}function ze(){return"#"+qe(this.r)+qe(this.g)+qe(this.b)}function Ue(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function qe(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function He(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new We(t,e,n,r)}function $e(t){if(t instanceof We)return new We(t.h,t.s,t.l,t.opacity);if(t instanceof _e||(t=Re(t)),!t)return new We;if(t instanceof We)return t;var e=(t=t.rgb()).r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),a=Math.max(e,n,r),o=NaN,s=a-i,c=(a+i)/2;return s?(o=e===a?(n-r)/s+6*(n0&&c<1?0:o,new We(o,s,c,t.opacity)}function We(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}function Ve(t,e,n){return 255*(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)}function Ge(t,e,n,r,i){var a=t*t,o=a*t;return((1-3*t+3*a-o)*e+(4-6*a+3*o)*n+(1+3*t+3*a-3*o)*r+o*i)/6}ve(_e,Re,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:Le,formatHex:Le,formatHsl:function(){return $e(this).formatHsl()},formatRgb:Ie,toString:Ie}),ve(Ye,je,be(_e,{brighter:function(t){return t=null==t?we:Math.pow(we,t),new Ye(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?xe:Math.pow(xe,t),new Ye(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:ze,formatHex:ze,formatRgb:Ue,toString:Ue})),ve(We,(function(t,e,n,r){return 1===arguments.length?$e(t):new We(t,e,n,null==r?1:r)}),be(_e,{brighter:function(t){return t=null==t?we:Math.pow(we,t),new We(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?xe:Math.pow(xe,t),new We(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new Ye(Ve(t>=240?t-240:t+120,i,r),Ve(t,i,r),Ve(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));const Xe=t=>()=>t;function Ze(t,e){var n=e-t;return n?function(t,e){return function(n){return t+n*e}}(t,n):Xe(isNaN(t)?e:t)}const Ke=function t(e){var n=function(t){return 1==(t=+t)?Ze:function(e,n){return n-e?function(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(r){return Math.pow(t+r*e,n)}}(e,n,t):Xe(isNaN(e)?n:e)}}(e);function r(t,e){var r=n((t=je(t)).r,(e=je(e)).r),i=n(t.g,e.g),a=n(t.b,e.b),o=Ze(t.opacity,e.opacity);return function(e){return t.r=r(e),t.g=i(e),t.b=a(e),t.opacity=o(e),t+""}}return r.gamma=t,r}(1);function Qe(t){return function(e){var n,r,i=e.length,a=new Array(i),o=new Array(i),s=new Array(i);for(n=0;n=1?(n=1,e-1):Math.floor(n*e),i=t[r],a=t[r+1],o=r>0?t[r-1]:2*i-a,s=ra&&(i=e.slice(a,i),s[o]?s[o]+=i:s[++o]=i),(n=n[0])===(r=r[0])?s[o]?s[o]+=r:s[++o]=r:(s[++o]=null,c.push({i:o,x:se(n,r)})),a=tn.lastIndex;return a=0&&(t=t.slice(0,e)),!t||"start"===t}))}(e)?ie:ae;return function(){var o=a(this,t),s=o.on;s!==r&&(i=(r=s).copy()).on(e,n),o.on=i}}(n,t,e))},attr:function(t,e){var n=P(t),r="transform"===n?pe:nn;return this.attrTween(t,"function"==typeof e?(n.local?un:cn)(n,r,me(this,"attr."+t,e)):null==e?(n.local?an:rn)(n):(n.local?sn:on)(n,r,e))},attrTween:function(t,e){var n="attr."+t;if(arguments.length<2)return(n=this.tween(n))&&n._value;if(null==e)return this.tween(n,null);if("function"!=typeof e)throw new Error;var r=P(t);return this.tween(n,(r.local?ln:hn)(r,e))},style:function(t,e,n){var r="transform"==(t+="")?de:nn;return null==e?this.styleTween(t,function(t,e){var n,r,i;return function(){var a=X(this,t),o=(this.style.removeProperty(t),X(this,t));return a===o?null:a===n&&o===r?i:i=e(n=a,r=o)}}(t,r)).on("end.style."+t,mn(t)):"function"==typeof e?this.styleTween(t,function(t,e,n){var r,i,a;return function(){var o=X(this,t),s=n(this),c=s+"";return null==s&&(this.style.removeProperty(t),c=s=X(this,t)),o===c?null:o===r&&c===i?a:(i=c,a=e(r=o,s))}}(t,r,me(this,"style."+t,e))).each(function(t,e){var n,r,i,a,o="style."+e,s="end."+o;return function(){var c=ae(this,t),u=c.on,l=null==c.value[o]?a||(a=mn(e)):void 0;u===n&&i===l||(r=(n=u).copy()).on(s,i=l),c.on=r}}(this._id,t)):this.styleTween(t,function(t,e,n){var r,i,a=n+"";return function(){var o=X(this,t);return o===a?null:o===r?i:i=e(r=o,n)}}(t,r,e),n).on("end.style."+t,null)},styleTween:function(t,e,n){var r="style."+(t+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(null==e)return this.tween(r,null);if("function"!=typeof e)throw new Error;return this.tween(r,function(t,e,n){var r,i;function a(){var a=e.apply(this,arguments);return a!==i&&(r=(i=a)&&function(t,e,n){return function(r){this.style.setProperty(t,e.call(this,r),n)}}(t,a,n)),r}return a._value=e,a}(t,e,null==n?"":n))},text:function(t){return this.tween("text","function"==typeof t?function(t){return function(){var e=t(this);this.textContent=null==e?"":e}}(me(this,"text",t)):function(t){return function(){this.textContent=t}}(null==t?"":t+""))},textTween:function(t){var e="text";if(arguments.length<1)return(e=this.tween(e))&&e._value;if(null==t)return this.tween(e,null);if("function"!=typeof t)throw new Error;return this.tween(e,function(t){var e,n;function r(){var r=t.apply(this,arguments);return r!==n&&(e=(n=r)&&function(t){return function(e){this.textContent=t.call(this,e)}}(r)),e}return r._value=t,r}(t))},remove:function(){return this.on("end.remove",function(t){return function(){var e=this.parentNode;for(var n in this.__transition)if(+n!==t)return;e&&e.removeChild(this)}}(this._id))},tween:function(t,e){var n=this._id;if(t+="",arguments.length<2){for(var r,i=oe(this.node(),n).tween,a=0,o=i.length;a2&&n.state<5,n.state=6,n.timer.stop(),n.on.call(r?"interrupt":"cancel",t,t.__data__,n.index,n.group),delete a[i]):o=!1;o&&delete t.__transition}}(this,t)}))},Nt.prototype.transition=function(t){var e,n;t instanceof bn?(e=t._id,t=t._name):(e=_n(),(n=wn).time=Vt(),t=null==t?null:t+"");for(var r=this._groups,i=r.length,a=0;a>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===n?Gn(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===n?Gn(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=Fn.exec(t))?new Kn(e[1],e[2],e[3],1):(e=Pn.exec(t))?new Kn(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=jn.exec(t))?Gn(e[1],e[2],e[3],e[4]):(e=Yn.exec(t))?Gn(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=zn.exec(t))?er(e[1],e[2]/100,e[3]/100,1):(e=Un.exec(t))?er(e[1],e[2]/100,e[3]/100,e[4]):qn.hasOwnProperty(t)?Vn(qn[t]):"transparent"===t?new Kn(NaN,NaN,NaN,0):null}function Vn(t){return new Kn(t>>16&255,t>>8&255,255&t,1)}function Gn(t,e,n,r){return r<=0&&(t=e=n=NaN),new Kn(t,e,n,r)}function Xn(t){return t instanceof Nn||(t=Wn(t)),t?new Kn((t=t.rgb()).r,t.g,t.b,t.opacity):new Kn}function Zn(t,e,n,r){return 1===arguments.length?Xn(t):new Kn(t,e,n,null==r?1:r)}function Kn(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}function Qn(){return"#"+tr(this.r)+tr(this.g)+tr(this.b)}function Jn(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function tr(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function er(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new rr(t,e,n,r)}function nr(t){if(t instanceof rr)return new rr(t.h,t.s,t.l,t.opacity);if(t instanceof Nn||(t=Wn(t)),!t)return new rr;if(t instanceof rr)return t;var e=(t=t.rgb()).r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),a=Math.max(e,n,r),o=NaN,s=a-i,c=(a+i)/2;return s?(o=e===a?(n-r)/s+6*(n0&&c<1?0:o,new rr(o,s,c,t.opacity)}function rr(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}function ir(t,e,n){return 255*(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)}An(Nn,Wn,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:Hn,formatHex:Hn,formatHsl:function(){return nr(this).formatHsl()},formatRgb:$n,toString:$n}),An(Kn,Zn,Mn(Nn,{brighter:function(t){return t=null==t?On:Math.pow(On,t),new Kn(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?Dn:Math.pow(Dn,t),new Kn(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:Qn,formatHex:Qn,formatRgb:Jn,toString:Jn})),An(rr,(function(t,e,n,r){return 1===arguments.length?nr(t):new rr(t,e,n,null==r?1:r)}),Mn(Nn,{brighter:function(t){return t=null==t?On:Math.pow(On,t),new rr(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?Dn:Math.pow(Dn,t),new rr(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new Kn(ir(t>=240?t-240:t+120,i,r),ir(t,i,r),ir(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));const ar=Math.PI/180,or=180/Math.PI,sr=.96422,cr=.82521,ur=4/29,lr=6/29,hr=3*lr*lr;function fr(t){if(t instanceof dr)return new dr(t.l,t.a,t.b,t.opacity);if(t instanceof br)return _r(t);t instanceof Kn||(t=Xn(t));var e,n,r=mr(t.r),i=mr(t.g),a=mr(t.b),o=pr((.2225045*r+.7168786*i+.0606169*a)/1);return r===i&&i===a?e=n=o:(e=pr((.4360747*r+.3850649*i+.1430804*a)/sr),n=pr((.0139322*r+.0971045*i+.7141733*a)/cr)),new dr(116*o-16,500*(e-o),200*(o-n),t.opacity)}function dr(t,e,n,r){this.l=+t,this.a=+e,this.b=+n,this.opacity=+r}function pr(t){return t>.008856451679035631?Math.pow(t,1/3):t/hr+ur}function yr(t){return t>lr?t*t*t:hr*(t-ur)}function gr(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function mr(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function vr(t,e,n,r){return 1===arguments.length?function(t){if(t instanceof br)return new br(t.h,t.c,t.l,t.opacity);if(t instanceof dr||(t=fr(t)),0===t.a&&0===t.b)return new br(NaN,0()=>t;function wr(t,e){return function(n){return t+n*e}}function kr(t,e){var n=e-t;return n?wr(t,n):xr(isNaN(t)?e:t)}function Tr(t){return function(e,n){var r=t((e=vr(e)).h,(n=vr(n)).h),i=kr(e.c,n.c),a=kr(e.l,n.l),o=kr(e.opacity,n.opacity);return function(t){return e.h=r(t),e.c=i(t),e.l=a(t),e.opacity=o(t),e+""}}}const Er=Tr((function(t,e){var n=e-t;return n?wr(t,n>180||n<-180?n-360*Math.round(n/360):n):xr(isNaN(t)?e:t)}));Tr(kr);var Cr=Math.sqrt(50),Sr=Math.sqrt(10),Ar=Math.sqrt(2);function Mr(t,e,n){var r=(e-t)/Math.max(0,n),i=Math.floor(Math.log(r)/Math.LN10),a=r/Math.pow(10,i);return i>=0?(a>=Cr?10:a>=Sr?5:a>=Ar?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(a>=Cr?10:a>=Sr?5:a>=Ar?2:1)}function Nr(t,e,n){var r=Math.abs(e-t)/Math.max(0,n),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),a=r/i;return a>=Cr?i*=10:a>=Sr?i*=5:a>=Ar&&(i*=2),ee?1:t>=e?0:NaN}function Or(t){let e=t,n=t,r=t;function i(t,e,i=0,a=t.length){if(i>>1;r(t[n],e)<0?i=n+1:a=n}while(it(e)-n,n=Dr,r=(e,n)=>Dr(t(e),n)),{left:i,center:function(t,n,r=0,a=t.length){const o=i(t,n,r,a-1);return o>r&&e(t[o-1],n)>-e(t[o],n)?o-1:o},right:function(t,e,i=0,a=t.length){if(i>>1;r(t[n],e)<=0?i=n+1:a=n}while(i>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===n?ni(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===n?ni(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=$r.exec(t))?new ii(e[1],e[2],e[3],1):(e=Wr.exec(t))?new ii(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=Vr.exec(t))?ni(e[1],e[2],e[3],e[4]):(e=Gr.exec(t))?ni(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=Xr.exec(t))?ci(e[1],e[2]/100,e[3]/100,1):(e=Zr.exec(t))?ci(e[1],e[2]/100,e[3]/100,e[4]):Kr.hasOwnProperty(t)?ei(Kr[t]):"transparent"===t?new ii(NaN,NaN,NaN,0):null}function ei(t){return new ii(t>>16&255,t>>8&255,255&t,1)}function ni(t,e,n,r){return r<=0&&(t=e=n=NaN),new ii(t,e,n,r)}function ri(t,e,n,r){return 1===arguments.length?function(t){return t instanceof Pr||(t=ti(t)),t?new ii((t=t.rgb()).r,t.g,t.b,t.opacity):new ii}(t):new ii(t,e,n,null==r?1:r)}function ii(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}function ai(){return"#"+si(this.r)+si(this.g)+si(this.b)}function oi(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function si(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function ci(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new li(t,e,n,r)}function ui(t){if(t instanceof li)return new li(t.h,t.s,t.l,t.opacity);if(t instanceof Pr||(t=ti(t)),!t)return new li;if(t instanceof li)return t;var e=(t=t.rgb()).r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),a=Math.max(e,n,r),o=NaN,s=a-i,c=(a+i)/2;return s?(o=e===a?(n-r)/s+6*(n0&&c<1?0:o,new li(o,s,c,t.opacity)}function li(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}function hi(t,e,n){return 255*(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)}function fi(t,e,n,r,i){var a=t*t,o=a*t;return((1-3*t+3*a-o)*e+(4-6*a+3*o)*n+(1+3*t+3*a-3*o)*r+o*i)/6}Rr(Pr,ti,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:Qr,formatHex:Qr,formatHsl:function(){return ui(this).formatHsl()},formatRgb:Jr,toString:Jr}),Rr(ii,ri,Fr(Pr,{brighter:function(t){return t=null==t?Yr:Math.pow(Yr,t),new ii(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?jr:Math.pow(jr,t),new ii(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:ai,formatHex:ai,formatRgb:oi,toString:oi})),Rr(li,(function(t,e,n,r){return 1===arguments.length?ui(t):new li(t,e,n,null==r?1:r)}),Fr(Pr,{brighter:function(t){return t=null==t?Yr:Math.pow(Yr,t),new li(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?jr:Math.pow(jr,t),new li(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new ii(hi(t>=240?t-240:t+120,i,r),hi(t,i,r),hi(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));const di=t=>()=>t;function pi(t,e){var n=e-t;return n?function(t,e){return function(n){return t+n*e}}(t,n):di(isNaN(t)?e:t)}const yi=function t(e){var n=function(t){return 1==(t=+t)?pi:function(e,n){return n-e?function(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(r){return Math.pow(t+r*e,n)}}(e,n,t):di(isNaN(e)?n:e)}}(e);function r(t,e){var r=n((t=ri(t)).r,(e=ri(e)).r),i=n(t.g,e.g),a=n(t.b,e.b),o=pi(t.opacity,e.opacity);return function(e){return t.r=r(e),t.g=i(e),t.b=a(e),t.opacity=o(e),t+""}}return r.gamma=t,r}(1);function gi(t){return function(e){var n,r,i=e.length,a=new Array(i),o=new Array(i),s=new Array(i);for(n=0;n=1?(n=1,e-1):Math.floor(n*e),i=t[r],a=t[r+1],o=r>0?t[r-1]:2*i-a,s=ra&&(i=e.slice(a,i),s[o]?s[o]+=i:s[++o]=i),(n=n[0])===(r=r[0])?s[o]?s[o]+=r:s[++o]=r:(s[++o]=null,c.push({i:o,x:bi(n,r)})),a=wi.lastIndex;return ae&&(n=t,t=e,e=n),u=function(n){return Math.max(t,Math.min(e,n))}),r=c>2?Oi:Di,i=a=null,h}function h(e){return null==e||isNaN(e=+e)?n:(i||(i=r(o.map(t),s,c)))(t(u(e)))}return h.invert=function(n){return u(e((a||(a=r(s,o.map(t),bi)))(n)))},h.domain=function(t){return arguments.length?(o=Array.from(t,Si),l()):o.slice()},h.range=function(t){return arguments.length?(s=Array.from(t),l()):s.slice()},h.rangeRound=function(t){return s=Array.from(t),c=Ci,l()},h.clamp=function(t){return arguments.length?(u=!!t||Mi,l()):u!==Mi},h.interpolate=function(t){return arguments.length?(c=t,l()):c},h.unknown=function(t){return arguments.length?(n=t,h):n},function(n,r){return t=n,e=r,l()}}()(Mi,Mi)}function Ii(t,e){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(e).domain(t)}return this}var Ri,Fi=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Pi(t){if(!(e=Fi.exec(t)))throw new Error("invalid format: "+t);var e;return new ji({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}function ji(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}function Yi(t,e){if((n=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"))<0)return null;var n,r=t.slice(0,n);return[r.length>1?r[0]+r.slice(2):r,+t.slice(n+1)]}function zi(t){return(t=Yi(Math.abs(t)))?t[1]:NaN}function Ui(t,e){var n=Yi(t,e);if(!n)return t+"";var r=n[0],i=n[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}Pi.prototype=ji.prototype,ji.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};const qi={"%":(t,e)=>(100*t).toFixed(e),b:t=>Math.round(t).toString(2),c:t=>t+"",d:function(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)},e:(t,e)=>t.toExponential(e),f:(t,e)=>t.toFixed(e),g:(t,e)=>t.toPrecision(e),o:t=>Math.round(t).toString(8),p:(t,e)=>Ui(100*t,e),r:Ui,s:function(t,e){var n=Yi(t,e);if(!n)return t+"";var r=n[0],i=n[1],a=i-(Ri=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,o=r.length;return a===o?r:a>o?r+new Array(a-o+1).join("0"):a>0?r.slice(0,a)+"."+r.slice(a):"0."+new Array(1-a).join("0")+Yi(t,Math.max(0,e+a-1))[0]},X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function Hi(t){return t}var $i,Wi,Vi,Gi=Array.prototype.map,Xi=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"];function Zi(){var t=Li();return t.copy=function(){return Bi(t,Zi())},Ii.apply(t,arguments),function(t){var e=t.domain;return t.ticks=function(t){var n=e();return function(t,e,n){var r,i,a,o,s=-1;if(n=+n,(t=+t)==(e=+e)&&n>0)return[t];if((r=e0){let n=Math.round(t/o),r=Math.round(e/o);for(n*oe&&--r,a=new Array(i=r-n+1);++se&&--r,a=new Array(i=r-n+1);++s0;){if((i=Mr(c,u,n))===r)return a[o]=c,a[s]=u,e(a);if(i>0)c=Math.floor(c/i)*i,u=Math.ceil(u/i)*i;else{if(!(i<0))break;c=Math.ceil(c*i)/i,u=Math.floor(u*i)/i}r=i}return t},t}(t)}$i=function(t){var e,n,r=void 0===t.grouping||void 0===t.thousands?Hi:(e=Gi.call(t.grouping,Number),n=t.thousands+"",function(t,r){for(var i=t.length,a=[],o=0,s=e[0],c=0;i>0&&s>0&&(c+s+1>r&&(s=Math.max(1,r-c)),a.push(t.substring(i-=s,i+s)),!((c+=s+1)>r));)s=e[o=(o+1)%e.length];return a.reverse().join(n)}),i=void 0===t.currency?"":t.currency[0]+"",a=void 0===t.currency?"":t.currency[1]+"",o=void 0===t.decimal?".":t.decimal+"",s=void 0===t.numerals?Hi:function(t){return function(e){return e.replace(/[0-9]/g,(function(e){return t[+e]}))}}(Gi.call(t.numerals,String)),c=void 0===t.percent?"%":t.percent+"",u=void 0===t.minus?"\u2212":t.minus+"",l=void 0===t.nan?"NaN":t.nan+"";function h(t){var e=(t=Pi(t)).fill,n=t.align,h=t.sign,f=t.symbol,d=t.zero,p=t.width,y=t.comma,g=t.precision,m=t.trim,v=t.type;"n"===v?(y=!0,v="g"):qi[v]||(void 0===g&&(g=12),m=!0,v="g"),(d||"0"===e&&"="===n)&&(d=!0,e="0",n="=");var b="$"===f?i:"#"===f&&/[boxX]/.test(v)?"0"+v.toLowerCase():"",_="$"===f?a:/[%p]/.test(v)?c:"",x=qi[v],w=/[defgprs%]/.test(v);function k(t){var i,a,c,f=b,k=_;if("c"===v)k=x(t)+k,t="";else{var T=(t=+t)<0||1/t<0;if(t=isNaN(t)?l:x(Math.abs(t),g),m&&(t=function(t){t:for(var e,n=t.length,r=1,i=-1;r0&&(i=0)}return i>0?t.slice(0,i)+t.slice(e+1):t}(t)),T&&0==+t&&"+"!==h&&(T=!1),f=(T?"("===h?h:u:"-"===h||"("===h?"":h)+f,k=("s"===v?Xi[8+Ri/3]:"")+k+(T&&"("===h?")":""),w)for(i=-1,a=t.length;++i(c=t.charCodeAt(i))||c>57){k=(46===c?o+t.slice(i+1):t.slice(i))+k,t=t.slice(0,i);break}}y&&!d&&(t=r(t,1/0));var E=f.length+t.length+k.length,C=E>1)+f+t+k+C.slice(E);break;default:t=C+f+t+k}return s(t)}return g=void 0===g?6:/[gprs]/.test(v)?Math.max(1,Math.min(21,g)):Math.max(0,Math.min(20,g)),k.toString=function(){return t+""},k}return{format:h,formatPrefix:function(t,e){var n=h(((t=Pi(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(zi(e)/3))),i=Math.pow(10,-r),a=Xi[8+r/3];return function(t){return n(i*t)+a}}}}({thousands:",",grouping:[3],currency:["$",""]}),Wi=$i.format,Vi=$i.formatPrefix;class Ki extends Map{constructor(t,e=Ji){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:e}}),null!=t)for(const[n,r]of t)this.set(n,r)}get(t){return super.get(Qi(this,t))}has(t){return super.has(Qi(this,t))}set(t,e){return super.set(function({_intern:t,_key:e},n){const r=e(n);return t.has(r)?t.get(r):(t.set(r,n),n)}(this,t),e)}delete(t){return super.delete(function({_intern:t,_key:e},n){const r=e(n);return t.has(r)&&(n=t.get(r),t.delete(r)),n}(this,t))}}function Qi({_intern:t,_key:e},n){const r=e(n);return t.has(r)?t.get(r):n}function Ji(t){return null!==t&&"object"==typeof t?t.valueOf():t}Set;const ta=Symbol("implicit");function ea(){var t=new Ki,e=[],n=[],r=ta;function i(i){let a=t.get(i);if(void 0===a){if(r!==ta)return r;t.set(i,a=e.push(i)-1)}return n[a%n.length]}return i.domain=function(n){if(!arguments.length)return e.slice();e=[],t=new Ki;for(const r of n)t.has(r)||t.set(r,e.push(r)-1);return i},i.range=function(t){return arguments.length?(n=Array.from(t),i):n.slice()},i.unknown=function(t){return arguments.length?(r=t,i):r},i.copy=function(){return ea(e,n).unknown(r)},Ii.apply(i,arguments),i}const na=1e3,ra=6e4,ia=36e5,aa=864e5,oa=6048e5,sa=31536e6;var ca=new Date,ua=new Date;function la(t,e,n,r){function i(e){return t(e=0===arguments.length?new Date:new Date(+e)),e}return i.floor=function(e){return t(e=new Date(+e)),e},i.ceil=function(n){return t(n=new Date(n-1)),e(n,1),t(n),n},i.round=function(t){var e=i(t),n=i.ceil(t);return t-e0))return s;do{s.push(o=new Date(+n)),e(n,a),t(n)}while(o=e)for(;t(e),!n(e);)e.setTime(e-1)}),(function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;e(t,-1),!n(t););else for(;--r>=0;)for(;e(t,1),!n(t););}))},n&&(i.count=function(e,r){return ca.setTime(+e),ua.setTime(+r),t(ca),t(ua),Math.floor(n(ca,ua))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(e){return r(e)%t==0}:function(e){return i.count(0,e)%t==0}):i:null}),i}var ha=la((function(){}),(function(t,e){t.setTime(+t+e)}),(function(t,e){return e-t}));ha.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?la((function(e){e.setTime(Math.floor(e/t)*t)}),(function(e,n){e.setTime(+e+n*t)}),(function(e,n){return(n-e)/t})):ha:null};const fa=ha;ha.range;var da=la((function(t){t.setTime(t-t.getMilliseconds())}),(function(t,e){t.setTime(+t+e*na)}),(function(t,e){return(e-t)/na}),(function(t){return t.getUTCSeconds()}));const pa=da;da.range;var ya=la((function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*na)}),(function(t,e){t.setTime(+t+e*ra)}),(function(t,e){return(e-t)/ra}),(function(t){return t.getMinutes()}));const ga=ya;ya.range;var ma=la((function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*na-t.getMinutes()*ra)}),(function(t,e){t.setTime(+t+e*ia)}),(function(t,e){return(e-t)/ia}),(function(t){return t.getHours()}));const va=ma;ma.range;var ba=la((t=>t.setHours(0,0,0,0)),((t,e)=>t.setDate(t.getDate()+e)),((t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*ra)/aa),(t=>t.getDate()-1));const _a=ba;function xa(t){return la((function(e){e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+7*e)}),(function(t,e){return(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*ra)/oa}))}ba.range;var wa=xa(0),ka=xa(1),Ta=xa(2),Ea=xa(3),Ca=xa(4),Sa=xa(5),Aa=xa(6),Ma=(wa.range,ka.range,Ta.range,Ea.range,Ca.range,Sa.range,Aa.range,la((function(t){t.setDate(1),t.setHours(0,0,0,0)}),(function(t,e){t.setMonth(t.getMonth()+e)}),(function(t,e){return e.getMonth()-t.getMonth()+12*(e.getFullYear()-t.getFullYear())}),(function(t){return t.getMonth()})));const Na=Ma;Ma.range;var Da=la((function(t){t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,e){t.setFullYear(t.getFullYear()+e)}),(function(t,e){return e.getFullYear()-t.getFullYear()}),(function(t){return t.getFullYear()}));Da.every=function(t){return isFinite(t=Math.floor(t))&&t>0?la((function(e){e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)}),(function(e,n){e.setFullYear(e.getFullYear()+n*t)})):null};const Oa=Da;Da.range;var Ba=la((function(t){t.setUTCSeconds(0,0)}),(function(t,e){t.setTime(+t+e*ra)}),(function(t,e){return(e-t)/ra}),(function(t){return t.getUTCMinutes()}));const La=Ba;Ba.range;var Ia=la((function(t){t.setUTCMinutes(0,0,0)}),(function(t,e){t.setTime(+t+e*ia)}),(function(t,e){return(e-t)/ia}),(function(t){return t.getUTCHours()}));const Ra=Ia;Ia.range;var Fa=la((function(t){t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+e)}),(function(t,e){return(e-t)/aa}),(function(t){return t.getUTCDate()-1}));const Pa=Fa;function ja(t){return la((function(e){e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+7*e)}),(function(t,e){return(e-t)/oa}))}Fa.range;var Ya=ja(0),za=ja(1),Ua=ja(2),qa=ja(3),Ha=ja(4),$a=ja(5),Wa=ja(6),Va=(Ya.range,za.range,Ua.range,qa.range,Ha.range,$a.range,Wa.range,la((function(t){t.setUTCDate(1),t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCMonth(t.getUTCMonth()+e)}),(function(t,e){return e.getUTCMonth()-t.getUTCMonth()+12*(e.getUTCFullYear()-t.getUTCFullYear())}),(function(t){return t.getUTCMonth()})));const Ga=Va;Va.range;var Xa=la((function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCFullYear(t.getUTCFullYear()+e)}),(function(t,e){return e.getUTCFullYear()-t.getUTCFullYear()}),(function(t){return t.getUTCFullYear()}));Xa.every=function(t){return isFinite(t=Math.floor(t))&&t>0?la((function(e){e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)}),(function(e,n){e.setUTCFullYear(e.getUTCFullYear()+n*t)})):null};const Za=Xa;function Ka(t,e,n,r,i,a){const o=[[pa,1,na],[pa,5,5e3],[pa,15,15e3],[pa,30,3e4],[a,1,ra],[a,5,3e5],[a,15,9e5],[a,30,18e5],[i,1,ia],[i,3,108e5],[i,6,216e5],[i,12,432e5],[r,1,aa],[r,2,1728e5],[n,1,oa],[e,1,2592e6],[e,3,7776e6],[t,1,sa]];function s(e,n,r){const i=Math.abs(n-e)/r,a=Or((([,,t])=>t)).right(o,i);if(a===o.length)return t.every(Nr(e/sa,n/sa,r));if(0===a)return fa.every(Math.max(Nr(e,n,r),1));const[s,c]=o[i/o[a-1][2][t.toLowerCase(),e])))}function go(t,e,n){var r=co.exec(e.slice(n,n+1));return r?(t.w=+r[0],n+r[0].length):-1}function mo(t,e,n){var r=co.exec(e.slice(n,n+1));return r?(t.u=+r[0],n+r[0].length):-1}function vo(t,e,n){var r=co.exec(e.slice(n,n+2));return r?(t.U=+r[0],n+r[0].length):-1}function bo(t,e,n){var r=co.exec(e.slice(n,n+2));return r?(t.V=+r[0],n+r[0].length):-1}function _o(t,e,n){var r=co.exec(e.slice(n,n+2));return r?(t.W=+r[0],n+r[0].length):-1}function xo(t,e,n){var r=co.exec(e.slice(n,n+4));return r?(t.y=+r[0],n+r[0].length):-1}function wo(t,e,n){var r=co.exec(e.slice(n,n+2));return r?(t.y=+r[0]+(+r[0]>68?1900:2e3),n+r[0].length):-1}function ko(t,e,n){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(n,n+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),n+r[0].length):-1}function To(t,e,n){var r=co.exec(e.slice(n,n+1));return r?(t.q=3*r[0]-3,n+r[0].length):-1}function Eo(t,e,n){var r=co.exec(e.slice(n,n+2));return r?(t.m=r[0]-1,n+r[0].length):-1}function Co(t,e,n){var r=co.exec(e.slice(n,n+2));return r?(t.d=+r[0],n+r[0].length):-1}function So(t,e,n){var r=co.exec(e.slice(n,n+3));return r?(t.m=0,t.d=+r[0],n+r[0].length):-1}function Ao(t,e,n){var r=co.exec(e.slice(n,n+2));return r?(t.H=+r[0],n+r[0].length):-1}function Mo(t,e,n){var r=co.exec(e.slice(n,n+2));return r?(t.M=+r[0],n+r[0].length):-1}function No(t,e,n){var r=co.exec(e.slice(n,n+2));return r?(t.S=+r[0],n+r[0].length):-1}function Do(t,e,n){var r=co.exec(e.slice(n,n+3));return r?(t.L=+r[0],n+r[0].length):-1}function Oo(t,e,n){var r=co.exec(e.slice(n,n+6));return r?(t.L=Math.floor(r[0]/1e3),n+r[0].length):-1}function Bo(t,e,n){var r=uo.exec(e.slice(n,n+1));return r?n+r[0].length:-1}function Lo(t,e,n){var r=co.exec(e.slice(n));return r?(t.Q=+r[0],n+r[0].length):-1}function Io(t,e,n){var r=co.exec(e.slice(n));return r?(t.s=+r[0],n+r[0].length):-1}function Ro(t,e){return ho(t.getDate(),e,2)}function Fo(t,e){return ho(t.getHours(),e,2)}function Po(t,e){return ho(t.getHours()%12||12,e,2)}function jo(t,e){return ho(1+_a.count(Oa(t),t),e,3)}function Yo(t,e){return ho(t.getMilliseconds(),e,3)}function zo(t,e){return Yo(t,e)+"000"}function Uo(t,e){return ho(t.getMonth()+1,e,2)}function qo(t,e){return ho(t.getMinutes(),e,2)}function Ho(t,e){return ho(t.getSeconds(),e,2)}function $o(t){var e=t.getDay();return 0===e?7:e}function Wo(t,e){return ho(wa.count(Oa(t)-1,t),e,2)}function Vo(t){var e=t.getDay();return e>=4||0===e?Ca(t):Ca.ceil(t)}function Go(t,e){return t=Vo(t),ho(Ca.count(Oa(t),t)+(4===Oa(t).getDay()),e,2)}function Xo(t){return t.getDay()}function Zo(t,e){return ho(ka.count(Oa(t)-1,t),e,2)}function Ko(t,e){return ho(t.getFullYear()%100,e,2)}function Qo(t,e){return ho((t=Vo(t)).getFullYear()%100,e,2)}function Jo(t,e){return ho(t.getFullYear()%1e4,e,4)}function ts(t,e){var n=t.getDay();return ho((t=n>=4||0===n?Ca(t):Ca.ceil(t)).getFullYear()%1e4,e,4)}function es(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+ho(e/60|0,"0",2)+ho(e%60,"0",2)}function ns(t,e){return ho(t.getUTCDate(),e,2)}function rs(t,e){return ho(t.getUTCHours(),e,2)}function is(t,e){return ho(t.getUTCHours()%12||12,e,2)}function as(t,e){return ho(1+Pa.count(Za(t),t),e,3)}function os(t,e){return ho(t.getUTCMilliseconds(),e,3)}function ss(t,e){return os(t,e)+"000"}function cs(t,e){return ho(t.getUTCMonth()+1,e,2)}function us(t,e){return ho(t.getUTCMinutes(),e,2)}function ls(t,e){return ho(t.getUTCSeconds(),e,2)}function hs(t){var e=t.getUTCDay();return 0===e?7:e}function fs(t,e){return ho(Ya.count(Za(t)-1,t),e,2)}function ds(t){var e=t.getUTCDay();return e>=4||0===e?Ha(t):Ha.ceil(t)}function ps(t,e){return t=ds(t),ho(Ha.count(Za(t),t)+(4===Za(t).getUTCDay()),e,2)}function ys(t){return t.getUTCDay()}function gs(t,e){return ho(za.count(Za(t)-1,t),e,2)}function ms(t,e){return ho(t.getUTCFullYear()%100,e,2)}function vs(t,e){return ho((t=ds(t)).getUTCFullYear()%100,e,2)}function bs(t,e){return ho(t.getUTCFullYear()%1e4,e,4)}function _s(t,e){var n=t.getUTCDay();return ho((t=n>=4||0===n?Ha(t):Ha.ceil(t)).getUTCFullYear()%1e4,e,4)}function xs(){return"+0000"}function ws(){return"%"}function ks(t){return+t}function Ts(t){return Math.floor(+t/1e3)}function Es(t){return new Date(t)}function Cs(t){return t instanceof Date?+t:+new Date(+t)}function Ss(t,e,n,r,i,a,o,s,c,u){var l=Li(),h=l.invert,f=l.domain,d=u(".%L"),p=u(":%S"),y=u("%I:%M"),g=u("%I %p"),m=u("%a %d"),v=u("%b %d"),b=u("%B"),_=u("%Y");function x(t){return(c(t)=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:ks,s:Ts,S:Ho,u:$o,U:Wo,V:Go,w:Xo,W:Zo,x:null,X:null,y:Ko,Y:Jo,Z:es,"%":ws},_={a:function(t){return o[t.getUTCDay()]},A:function(t){return a[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return s[t.getUTCMonth()]},c:null,d:ns,e:ns,f:ss,g:vs,G:_s,H:rs,I:is,j:as,L:os,m:cs,M:us,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:ks,s:Ts,S:ls,u:hs,U:fs,V:ps,w:ys,W:gs,x:null,X:null,y:ms,Y:bs,Z:xs,"%":ws},x={a:function(t,e,n){var r=d.exec(e.slice(n));return r?(t.w=p.get(r[0].toLowerCase()),n+r[0].length):-1},A:function(t,e,n){var r=h.exec(e.slice(n));return r?(t.w=f.get(r[0].toLowerCase()),n+r[0].length):-1},b:function(t,e,n){var r=m.exec(e.slice(n));return r?(t.m=v.get(r[0].toLowerCase()),n+r[0].length):-1},B:function(t,e,n){var r=y.exec(e.slice(n));return r?(t.m=g.get(r[0].toLowerCase()),n+r[0].length):-1},c:function(t,n,r){return T(t,e,n,r)},d:Co,e:Co,f:Oo,g:wo,G:xo,H:Ao,I:Ao,j:So,L:Do,m:Eo,M:Mo,p:function(t,e,n){var r=u.exec(e.slice(n));return r?(t.p=l.get(r[0].toLowerCase()),n+r[0].length):-1},q:To,Q:Lo,s:Io,S:No,u:mo,U:vo,V:bo,w:go,W:_o,x:function(t,e,r){return T(t,n,e,r)},X:function(t,e,n){return T(t,r,e,n)},y:wo,Y:xo,Z:ko,"%":Bo};function w(t,e){return function(n){var r,i,a,o=[],s=-1,c=0,u=t.length;for(n instanceof Date||(n=new Date(+n));++s53)return null;"w"in a||(a.w=1),"Z"in a?(i=(r=ro(io(a.y,0,1))).getUTCDay(),r=i>4||0===i?za.ceil(r):za(r),r=Pa.offset(r,7*(a.V-1)),a.y=r.getUTCFullYear(),a.m=r.getUTCMonth(),a.d=r.getUTCDate()+(a.w+6)%7):(i=(r=no(io(a.y,0,1))).getDay(),r=i>4||0===i?ka.ceil(r):ka(r),r=_a.offset(r,7*(a.V-1)),a.y=r.getFullYear(),a.m=r.getMonth(),a.d=r.getDate()+(a.w+6)%7)}else("W"in a||"U"in a)&&("w"in a||(a.w="u"in a?a.u%7:"W"in a?1:0),i="Z"in a?ro(io(a.y,0,1)).getUTCDay():no(io(a.y,0,1)).getDay(),a.m=0,a.d="W"in a?(a.w+6)%7+7*a.W-(i+5)%7:a.w+7*a.U-(i+6)%7);return"Z"in a?(a.H+=a.Z/100|0,a.M+=a.Z%100,ro(a)):no(a)}}function T(t,e,n,r){for(var i,a,o=0,s=e.length,c=n.length;o=c)return-1;if(37===(i=e.charCodeAt(o++))){if(i=e.charAt(o++),!(a=x[i in so?e.charAt(o++):i])||(r=a(t,n,r))<0)return-1}else if(i!=n.charCodeAt(r++))return-1}return r}return b.x=w(n,b),b.X=w(r,b),b.c=w(e,b),_.x=w(n,_),_.X=w(r,_),_.c=w(e,_),{format:function(t){var e=w(t+="",b);return e.toString=function(){return t},e},parse:function(t){var e=k(t+="",!1);return e.toString=function(){return t},e},utcFormat:function(t){var e=w(t+="",_);return e.toString=function(){return t},e},utcParse:function(t){var e=k(t+="",!0);return e.toString=function(){return t},e}}}({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}),oo=ao.format,ao.parse,ao.utcFormat,ao.utcParse;var Is=Array.prototype.find;function Rs(){return this.firstElementChild}var Fs=Array.prototype.filter;function Ps(){return Array.from(this.children)}function js(t){return new Array(t.length)}function Ys(t,e){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=e}function zs(t,e,n,r,i,a){for(var o,s=0,c=e.length,u=a.length;se?1:t>=e?0:NaN}Ys.prototype={constructor:Ys,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,e){return this._parent.insertBefore(t,e)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};var Ws="http://www.w3.org/1999/xhtml";const Vs={svg:"http://www.w3.org/2000/svg",xhtml:Ws,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};function Gs(t){var e=t+="",n=e.indexOf(":");return n>=0&&"xmlns"!==(e=t.slice(0,n))&&(t=t.slice(n+1)),Vs.hasOwnProperty(e)?{space:Vs[e],local:t}:t}function Xs(t){return function(){this.removeAttribute(t)}}function Zs(t){return function(){this.removeAttributeNS(t.space,t.local)}}function Ks(t,e){return function(){this.setAttribute(t,e)}}function Qs(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function Js(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttribute(t):this.setAttribute(t,n)}}function tc(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,n)}}function ec(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function nc(t){return function(){this.style.removeProperty(t)}}function rc(t,e,n){return function(){this.style.setProperty(t,e,n)}}function ic(t,e,n){return function(){var r=e.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,n)}}function ac(t,e){return t.style.getPropertyValue(e)||ec(t).getComputedStyle(t,null).getPropertyValue(e)}function oc(t){return function(){delete this[t]}}function sc(t,e){return function(){this[t]=e}}function cc(t,e){return function(){var n=e.apply(this,arguments);null==n?delete this[t]:this[t]=n}}function uc(t){return t.trim().split(/^|\s+/)}function lc(t){return t.classList||new hc(t)}function hc(t){this._node=t,this._names=uc(t.getAttribute("class")||"")}function fc(t,e){for(var n=lc(t),r=-1,i=e.length;++r=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var Fc=[null];function Pc(t,e){this._groups=t,this._parents=e}function jc(){return new Pc([[document.documentElement]],Fc)}Pc.prototype=jc.prototype={constructor:Pc,select:function(t){"function"!=typeof t&&(t=Ms(t));for(var e=this._groups,n=e.length,r=new Array(n),i=0;i=_&&(_=b+1);!(v=g[_])&&++_=0;)(r=i[a])&&(o&&4^r.compareDocumentPosition(o)&&o.parentNode.insertBefore(r,o),o=r);return this},sort:function(t){function e(e,n){return e&&n?t(e.__data__,n.__data__):!e-!n}t||(t=$s);for(var n=this._groups,r=n.length,i=new Array(r),a=0;a1?this.each((null==e?nc:"function"==typeof e?ic:rc)(t,e,null==n?"":n)):ac(this.node(),t)},property:function(t,e){return arguments.length>1?this.each((null==e?oc:"function"==typeof e?cc:sc)(t,e)):this.node()[t]},classed:function(t,e){var n=uc(t+"");if(arguments.length<2){for(var r=lc(this.node()),i=-1,a=n.length;++i=0&&(e=t.slice(n+1),t=t.slice(0,n)),{type:t,name:e}}))}(t+""),o=a.length;if(!(arguments.length<2)){for(s=e?Bc:Oc,r=0;r$c)if(Math.abs(l*s-c*u)>$c&&i){var f=n-a,d=r-o,p=s*s+c*c,y=f*f+d*d,g=Math.sqrt(p),m=Math.sqrt(h),v=i*Math.tan((qc-Math.acos((p+h-y)/(2*g*m)))/2),b=v/m,_=v/g;Math.abs(b-1)>$c&&(this._+="L"+(t+b*u)+","+(e+b*l)),this._+="A"+i+","+i+",0,0,"+ +(l*f>u*d)+","+(this._x1=t+_*s)+","+(this._y1=e+_*c)}else this._+="L"+(this._x1=t)+","+(this._y1=e)},arc:function(t,e,n,r,i,a){t=+t,e=+e,a=!!a;var o=(n=+n)*Math.cos(r),s=n*Math.sin(r),c=t+o,u=e+s,l=1^a,h=a?r-i:i-r;if(n<0)throw new Error("negative radius: "+n);null===this._x1?this._+="M"+c+","+u:(Math.abs(this._x1-c)>$c||Math.abs(this._y1-u)>$c)&&(this._+="L"+c+","+u),n&&(h<0&&(h=h%Hc+Hc),h>Wc?this._+="A"+n+","+n+",0,1,"+l+","+(t-o)+","+(e-s)+"A"+n+","+n+",0,1,"+l+","+(this._x1=c)+","+(this._y1=u):h>$c&&(this._+="A"+n+","+n+",0,"+ +(h>=qc)+","+l+","+(this._x1=t+n*Math.cos(i))+","+(this._y1=e+n*Math.sin(i))))},rect:function(t,e,n,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +n+"v"+ +r+"h"+-n+"Z"},toString:function(){return this._}};const Xc=Gc;function Zc(t){return function(){return t}}var Kc=Math.abs,Qc=Math.atan2,Jc=Math.cos,tu=Math.max,eu=Math.min,nu=Math.sin,ru=Math.sqrt,iu=1e-12,au=Math.PI,ou=au/2,su=2*au;function cu(t){return t>=1?ou:t<=-1?-ou:Math.asin(t)}function uu(t){return t.innerRadius}function lu(t){return t.outerRadius}function hu(t){return t.startAngle}function fu(t){return t.endAngle}function du(t){return t&&t.padAngle}function pu(t,e,n,r,i,a,o){var s=t-n,c=e-r,u=(o?a:-a)/ru(s*s+c*c),l=u*c,h=-u*s,f=t+l,d=e+h,p=n+l,y=r+h,g=(f+p)/2,m=(d+y)/2,v=p-f,b=y-d,_=v*v+b*b,x=i-a,w=f*y-p*d,k=(b<0?-1:1)*ru(tu(0,x*x*_-w*w)),T=(w*b-v*k)/_,E=(-w*v-b*k)/_,C=(w*b+v*k)/_,S=(-w*v+b*k)/_,A=T-g,M=E-m,N=C-g,D=S-m;return A*A+M*M>N*N+D*D&&(T=C,E=S),{cx:T,cy:E,x01:-l,y01:-h,x11:T*(i/x-1),y11:E*(i/x-1)}}function yu(){var t=uu,e=lu,n=Zc(0),r=null,i=hu,a=fu,o=du,s=null;function c(){var c,u,l=+t.apply(this,arguments),h=+e.apply(this,arguments),f=i.apply(this,arguments)-ou,d=a.apply(this,arguments)-ou,p=Kc(d-f),y=d>f;if(s||(s=c=Xc()),hiu)if(p>su-iu)s.moveTo(h*Jc(f),h*nu(f)),s.arc(0,0,h,f,d,!y),l>iu&&(s.moveTo(l*Jc(d),l*nu(d)),s.arc(0,0,l,d,f,y));else{var g,m,v=f,b=d,_=f,x=d,w=p,k=p,T=o.apply(this,arguments)/2,E=T>iu&&(r?+r.apply(this,arguments):ru(l*l+h*h)),C=eu(Kc(h-l)/2,+n.apply(this,arguments)),S=C,A=C;if(E>iu){var M=cu(E/l*nu(T)),N=cu(E/h*nu(T));(w-=2*M)>iu?(_+=M*=y?1:-1,x-=M):(w=0,_=x=(f+d)/2),(k-=2*N)>iu?(v+=N*=y?1:-1,b-=N):(k=0,v=b=(f+d)/2)}var D=h*Jc(v),O=h*nu(v),B=l*Jc(x),L=l*nu(x);if(C>iu){var I,R=h*Jc(b),F=h*nu(b),P=l*Jc(_),j=l*nu(_);if(p1?0:t<-1?au:Math.acos(t)}((Y*U+z*q)/(ru(Y*Y+z*z)*ru(U*U+q*q)))/2),$=ru(I[0]*I[0]+I[1]*I[1]);S=eu(C,(l-$)/(H-1)),A=eu(C,(h-$)/(H+1))}}k>iu?A>iu?(g=pu(P,j,D,O,h,A,y),m=pu(R,F,B,L,h,A,y),s.moveTo(g.cx+g.x01,g.cy+g.y01),Aiu&&w>iu?S>iu?(g=pu(B,L,R,F,l,-S,y),m=pu(D,O,P,j,l,-S,y),s.lineTo(g.cx+g.x01,g.cy+g.y01),St?1:e>=t?0:NaN}function ku(t){return t}function Tu(){}function Eu(t,e,n){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+n)/6)}function Cu(t){this._context=t}function Su(t){return new Cu(t)}function Au(t){this._context=t}function Mu(t){this._context=t}function Nu(t){this._context=t}function Du(t){return t<0?-1:1}function Ou(t,e,n){var r=t._x1-t._x0,i=e-t._x1,a=(t._y1-t._y0)/(r||i<0&&-0),o=(n-t._y1)/(i||r<0&&-0),s=(a*i+o*r)/(r+i);return(Du(a)+Du(o))*Math.min(Math.abs(a),Math.abs(o),.5*Math.abs(s))||0}function Bu(t,e){var n=t._x1-t._x0;return n?(3*(t._y1-t._y0)/n-e)/2:e}function Lu(t,e,n){var r=t._x0,i=t._y0,a=t._x1,o=t._y1,s=(a-r)/3;t._context.bezierCurveTo(r+s,i+s*e,a-s,o-s*n,a,o)}function Iu(t){this._context=t}function Ru(t){this._context=new Fu(t)}function Fu(t){this._context=t}function Pu(t){this._context=t}function ju(t){var e,n,r=t.length-1,i=new Array(r),a=new Array(r),o=new Array(r);for(i[0]=0,a[0]=2,o[0]=t[0]+2*t[1],e=1;e=0;--e)i[e]=(o[e]-i[e+1])/a[e];for(a[r-1]=(t[r]+i[r-1])/2,e=0;e=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var n=this._x*(1-this._t)+t*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,e)}}this._x=t,this._y=e}};var zu=new Date,Uu=new Date;function qu(t,e,n,r){function i(e){return t(e=0===arguments.length?new Date:new Date(+e)),e}return i.floor=function(e){return t(e=new Date(+e)),e},i.ceil=function(n){return t(n=new Date(n-1)),e(n,1),t(n),n},i.round=function(t){var e=i(t),n=i.ceil(t);return t-e0))return s;do{s.push(o=new Date(+n)),e(n,a),t(n)}while(o=e)for(;t(e),!n(e);)e.setTime(e-1)}),(function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;e(t,-1),!n(t););else for(;--r>=0;)for(;e(t,1),!n(t););}))},n&&(i.count=function(e,r){return zu.setTime(+e),Uu.setTime(+r),t(zu),t(Uu),Math.floor(n(zu,Uu))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(e){return r(e)%t==0}:function(e){return i.count(0,e)%t==0}):i:null}),i}const Hu=864e5,$u=6048e5;function Wu(t){return qu((function(e){e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+7*e)}),(function(t,e){return(e-t)/$u}))}var Vu=Wu(0),Gu=Wu(1),Xu=Wu(2),Zu=Wu(3),Ku=Wu(4),Qu=Wu(5),Ju=Wu(6),tl=(Vu.range,Gu.range,Xu.range,Zu.range,Ku.range,Qu.range,Ju.range,qu((function(t){t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+e)}),(function(t,e){return(e-t)/Hu}),(function(t){return t.getUTCDate()-1})));const el=tl;function nl(t){return qu((function(e){e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+7*e)}),(function(t,e){return(e-t-6e4*(e.getTimezoneOffset()-t.getTimezoneOffset()))/$u}))}tl.range;var rl=nl(0),il=nl(1),al=nl(2),ol=nl(3),sl=nl(4),cl=nl(5),ul=nl(6),ll=(rl.range,il.range,al.range,ol.range,sl.range,cl.range,ul.range,qu((t=>t.setHours(0,0,0,0)),((t,e)=>t.setDate(t.getDate()+e)),((t,e)=>(e-t-6e4*(e.getTimezoneOffset()-t.getTimezoneOffset()))/Hu),(t=>t.getDate()-1)));const hl=ll;ll.range;var fl=qu((function(t){t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,e){t.setFullYear(t.getFullYear()+e)}),(function(t,e){return e.getFullYear()-t.getFullYear()}),(function(t){return t.getFullYear()}));fl.every=function(t){return isFinite(t=Math.floor(t))&&t>0?qu((function(e){e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)}),(function(e,n){e.setFullYear(e.getFullYear()+n*t)})):null};const dl=fl;fl.range;var pl=qu((function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCFullYear(t.getUTCFullYear()+e)}),(function(t,e){return e.getUTCFullYear()-t.getUTCFullYear()}),(function(t){return t.getUTCFullYear()}));pl.every=function(t){return isFinite(t=Math.floor(t))&&t>0?qu((function(e){e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)}),(function(e,n){e.setUTCFullYear(e.getUTCFullYear()+n*t)})):null};const yl=pl;function gl(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function ml(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function vl(t,e,n){return{y:t,m:e,d:n,H:0,M:0,S:0,L:0}}pl.range;var bl,_l,xl={"-":"",_:" ",0:"0"},wl=/^\s*\d+/,kl=/^%/,Tl=/[\\^$*+?|[\]().{}]/g;function El(t,e,n){var r=t<0?"-":"",i=(r?-t:t)+"",a=i.length;return r+(a[t.toLowerCase(),e])))}function Ml(t,e,n){var r=wl.exec(e.slice(n,n+1));return r?(t.w=+r[0],n+r[0].length):-1}function Nl(t,e,n){var r=wl.exec(e.slice(n,n+1));return r?(t.u=+r[0],n+r[0].length):-1}function Dl(t,e,n){var r=wl.exec(e.slice(n,n+2));return r?(t.U=+r[0],n+r[0].length):-1}function Ol(t,e,n){var r=wl.exec(e.slice(n,n+2));return r?(t.V=+r[0],n+r[0].length):-1}function Bl(t,e,n){var r=wl.exec(e.slice(n,n+2));return r?(t.W=+r[0],n+r[0].length):-1}function Ll(t,e,n){var r=wl.exec(e.slice(n,n+4));return r?(t.y=+r[0],n+r[0].length):-1}function Il(t,e,n){var r=wl.exec(e.slice(n,n+2));return r?(t.y=+r[0]+(+r[0]>68?1900:2e3),n+r[0].length):-1}function Rl(t,e,n){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(n,n+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),n+r[0].length):-1}function Fl(t,e,n){var r=wl.exec(e.slice(n,n+1));return r?(t.q=3*r[0]-3,n+r[0].length):-1}function Pl(t,e,n){var r=wl.exec(e.slice(n,n+2));return r?(t.m=r[0]-1,n+r[0].length):-1}function jl(t,e,n){var r=wl.exec(e.slice(n,n+2));return r?(t.d=+r[0],n+r[0].length):-1}function Yl(t,e,n){var r=wl.exec(e.slice(n,n+3));return r?(t.m=0,t.d=+r[0],n+r[0].length):-1}function zl(t,e,n){var r=wl.exec(e.slice(n,n+2));return r?(t.H=+r[0],n+r[0].length):-1}function Ul(t,e,n){var r=wl.exec(e.slice(n,n+2));return r?(t.M=+r[0],n+r[0].length):-1}function ql(t,e,n){var r=wl.exec(e.slice(n,n+2));return r?(t.S=+r[0],n+r[0].length):-1}function Hl(t,e,n){var r=wl.exec(e.slice(n,n+3));return r?(t.L=+r[0],n+r[0].length):-1}function $l(t,e,n){var r=wl.exec(e.slice(n,n+6));return r?(t.L=Math.floor(r[0]/1e3),n+r[0].length):-1}function Wl(t,e,n){var r=kl.exec(e.slice(n,n+1));return r?n+r[0].length:-1}function Vl(t,e,n){var r=wl.exec(e.slice(n));return r?(t.Q=+r[0],n+r[0].length):-1}function Gl(t,e,n){var r=wl.exec(e.slice(n));return r?(t.s=+r[0],n+r[0].length):-1}function Xl(t,e){return El(t.getDate(),e,2)}function Zl(t,e){return El(t.getHours(),e,2)}function Kl(t,e){return El(t.getHours()%12||12,e,2)}function Ql(t,e){return El(1+hl.count(dl(t),t),e,3)}function Jl(t,e){return El(t.getMilliseconds(),e,3)}function th(t,e){return Jl(t,e)+"000"}function eh(t,e){return El(t.getMonth()+1,e,2)}function nh(t,e){return El(t.getMinutes(),e,2)}function rh(t,e){return El(t.getSeconds(),e,2)}function ih(t){var e=t.getDay();return 0===e?7:e}function ah(t,e){return El(rl.count(dl(t)-1,t),e,2)}function oh(t){var e=t.getDay();return e>=4||0===e?sl(t):sl.ceil(t)}function sh(t,e){return t=oh(t),El(sl.count(dl(t),t)+(4===dl(t).getDay()),e,2)}function ch(t){return t.getDay()}function uh(t,e){return El(il.count(dl(t)-1,t),e,2)}function lh(t,e){return El(t.getFullYear()%100,e,2)}function hh(t,e){return El((t=oh(t)).getFullYear()%100,e,2)}function fh(t,e){return El(t.getFullYear()%1e4,e,4)}function dh(t,e){var n=t.getDay();return El((t=n>=4||0===n?sl(t):sl.ceil(t)).getFullYear()%1e4,e,4)}function ph(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+El(e/60|0,"0",2)+El(e%60,"0",2)}function yh(t,e){return El(t.getUTCDate(),e,2)}function gh(t,e){return El(t.getUTCHours(),e,2)}function mh(t,e){return El(t.getUTCHours()%12||12,e,2)}function vh(t,e){return El(1+el.count(yl(t),t),e,3)}function bh(t,e){return El(t.getUTCMilliseconds(),e,3)}function _h(t,e){return bh(t,e)+"000"}function xh(t,e){return El(t.getUTCMonth()+1,e,2)}function wh(t,e){return El(t.getUTCMinutes(),e,2)}function kh(t,e){return El(t.getUTCSeconds(),e,2)}function Th(t){var e=t.getUTCDay();return 0===e?7:e}function Eh(t,e){return El(Vu.count(yl(t)-1,t),e,2)}function Ch(t){var e=t.getUTCDay();return e>=4||0===e?Ku(t):Ku.ceil(t)}function Sh(t,e){return t=Ch(t),El(Ku.count(yl(t),t)+(4===yl(t).getUTCDay()),e,2)}function Ah(t){return t.getUTCDay()}function Mh(t,e){return El(Gu.count(yl(t)-1,t),e,2)}function Nh(t,e){return El(t.getUTCFullYear()%100,e,2)}function Dh(t,e){return El((t=Ch(t)).getUTCFullYear()%100,e,2)}function Oh(t,e){return El(t.getUTCFullYear()%1e4,e,4)}function Bh(t,e){var n=t.getUTCDay();return El((t=n>=4||0===n?Ku(t):Ku.ceil(t)).getUTCFullYear()%1e4,e,4)}function Lh(){return"+0000"}function Ih(){return"%"}function Rh(t){return+t}function Fh(t){return Math.floor(+t/1e3)}bl=function(t){var e=t.dateTime,n=t.date,r=t.time,i=t.periods,a=t.days,o=t.shortDays,s=t.months,c=t.shortMonths,u=Sl(i),l=Al(i),h=Sl(a),f=Al(a),d=Sl(o),p=Al(o),y=Sl(s),g=Al(s),m=Sl(c),v=Al(c),b={a:function(t){return o[t.getDay()]},A:function(t){return a[t.getDay()]},b:function(t){return c[t.getMonth()]},B:function(t){return s[t.getMonth()]},c:null,d:Xl,e:Xl,f:th,g:hh,G:dh,H:Zl,I:Kl,j:Ql,L:Jl,m:eh,M:nh,p:function(t){return i[+(t.getHours()>=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:Rh,s:Fh,S:rh,u:ih,U:ah,V:sh,w:ch,W:uh,x:null,X:null,y:lh,Y:fh,Z:ph,"%":Ih},_={a:function(t){return o[t.getUTCDay()]},A:function(t){return a[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return s[t.getUTCMonth()]},c:null,d:yh,e:yh,f:_h,g:Dh,G:Bh,H:gh,I:mh,j:vh,L:bh,m:xh,M:wh,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:Rh,s:Fh,S:kh,u:Th,U:Eh,V:Sh,w:Ah,W:Mh,x:null,X:null,y:Nh,Y:Oh,Z:Lh,"%":Ih},x={a:function(t,e,n){var r=d.exec(e.slice(n));return r?(t.w=p.get(r[0].toLowerCase()),n+r[0].length):-1},A:function(t,e,n){var r=h.exec(e.slice(n));return r?(t.w=f.get(r[0].toLowerCase()),n+r[0].length):-1},b:function(t,e,n){var r=m.exec(e.slice(n));return r?(t.m=v.get(r[0].toLowerCase()),n+r[0].length):-1},B:function(t,e,n){var r=y.exec(e.slice(n));return r?(t.m=g.get(r[0].toLowerCase()),n+r[0].length):-1},c:function(t,n,r){return T(t,e,n,r)},d:jl,e:jl,f:$l,g:Il,G:Ll,H:zl,I:zl,j:Yl,L:Hl,m:Pl,M:Ul,p:function(t,e,n){var r=u.exec(e.slice(n));return r?(t.p=l.get(r[0].toLowerCase()),n+r[0].length):-1},q:Fl,Q:Vl,s:Gl,S:ql,u:Nl,U:Dl,V:Ol,w:Ml,W:Bl,x:function(t,e,r){return T(t,n,e,r)},X:function(t,e,n){return T(t,r,e,n)},y:Il,Y:Ll,Z:Rl,"%":Wl};function w(t,e){return function(n){var r,i,a,o=[],s=-1,c=0,u=t.length;for(n instanceof Date||(n=new Date(+n));++s53)return null;"w"in a||(a.w=1),"Z"in a?(i=(r=ml(vl(a.y,0,1))).getUTCDay(),r=i>4||0===i?Gu.ceil(r):Gu(r),r=el.offset(r,7*(a.V-1)),a.y=r.getUTCFullYear(),a.m=r.getUTCMonth(),a.d=r.getUTCDate()+(a.w+6)%7):(i=(r=gl(vl(a.y,0,1))).getDay(),r=i>4||0===i?il.ceil(r):il(r),r=hl.offset(r,7*(a.V-1)),a.y=r.getFullYear(),a.m=r.getMonth(),a.d=r.getDate()+(a.w+6)%7)}else("W"in a||"U"in a)&&("w"in a||(a.w="u"in a?a.u%7:"W"in a?1:0),i="Z"in a?ml(vl(a.y,0,1)).getUTCDay():gl(vl(a.y,0,1)).getDay(),a.m=0,a.d="W"in a?(a.w+6)%7+7*a.W-(i+5)%7:a.w+7*a.U-(i+6)%7);return"Z"in a?(a.H+=a.Z/100|0,a.M+=a.Z%100,ml(a)):gl(a)}}function T(t,e,n,r){for(var i,a,o=0,s=e.length,c=n.length;o=c)return-1;if(37===(i=e.charCodeAt(o++))){if(i=e.charAt(o++),!(a=x[i in xl?e.charAt(o++):i])||(r=a(t,n,r))<0)return-1}else if(i!=n.charCodeAt(r++))return-1}return r}return b.x=w(n,b),b.X=w(r,b),b.c=w(e,b),_.x=w(n,_),_.X=w(r,_),_.c=w(e,_),{format:function(t){var e=w(t+="",b);return e.toString=function(){return t},e},parse:function(t){var e=k(t+="",!1);return e.toString=function(){return t},e},utcFormat:function(t){var e=w(t+="",_);return e.toString=function(){return t},e},utcParse:function(t){var e=k(t+="",!0);return e.toString=function(){return t},e}}}({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}),_l=bl.format,bl.parse,bl.utcFormat,bl.utcParse;var Ph={value:()=>{}};function jh(){for(var t,e=0,n=arguments.length,r={};e=0&&(n=t.slice(r+1),t=t.slice(0,r)),t&&!e.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))}(t+"",r),a=-1,o=i.length;if(!(arguments.length<2)){if(null!=e&&"function"!=typeof e)throw new Error("invalid callback: "+e);for(;++a0)for(var n,r,i=new Array(n),a=0;a=0&&e._call.call(void 0,t),e=e._next;--Wh}()}finally{Wh=0,function(){for(var t,e,n=Hh,r=1/0;n;)n._call?(r>n._time&&(r=n._time),t=n,n=n._next):(e=n._next,n._next=null,n=t?t._next=e:Hh=e);$h=t,sf(r)}(),Zh=0}}function of(){var t=Qh.now(),e=t-Xh;e>1e3&&(Kh-=e,Xh=t)}function sf(t){Wh||(Vh&&(Vh=clearTimeout(Vh)),t-Zh>24?(t<1/0&&(Vh=setTimeout(af,t-Qh.now()-Kh)),Gh&&(Gh=clearInterval(Gh))):(Gh||(Xh=Qh.now(),Gh=setInterval(of,1e3)),Wh=1,Jh(af)))}function cf(t,e,n){var r=new nf;return e=null==e?0:+e,r.restart((n=>{r.stop(),t(n+e)}),e,n),r}nf.prototype=rf.prototype={constructor:nf,restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?tf():+n)+(null==e?0:+e),this._next||$h===this||($h?$h._next=this:Hh=this,$h=this),this._call=t,this._time=n,sf()},stop:function(){this._call&&(this._call=null,this._time=1/0,sf())}};var uf=qh("start","end","cancel","interrupt"),lf=[];function hf(t,e,n,r,i,a){var o=t.__transition;if(o){if(n in o)return}else t.__transition={};!function(t,e,n){var r,i=t.__transition;function a(c){var u,l,h,f;if(1!==n.state)return s();for(u in i)if((f=i[u]).name===n.name){if(3===f.state)return cf(a);4===f.state?(f.state=6,f.timer.stop(),f.on.call("interrupt",t,t.__data__,f.index,f.group),delete i[u]):+u0)throw new Error("too late; already scheduled");return n}function df(t,e){var n=pf(t,e);if(n.state>3)throw new Error("too late; already running");return n}function pf(t,e){var n=t.__transition;if(!n||!(n=n[e]))throw new Error("transition not found");return n}function yf(t,e){return t=+t,e=+e,function(n){return t*(1-n)+e*n}}var gf,mf=180/Math.PI,vf={translateX:0,translateY:0,rotate:0,skewX:0,scaleX:1,scaleY:1};function bf(t,e,n,r,i,a){var o,s,c;return(o=Math.sqrt(t*t+e*e))&&(t/=o,e/=o),(c=t*n+e*r)&&(n-=t*c,r-=e*c),(s=Math.sqrt(n*n+r*r))&&(n/=s,r/=s,c/=s),t*r180?e+=360:e-t>180&&(t+=360),a.push({i:n.push(i(n)+"rotate(",null,r)-2,x:yf(t,e)})):e&&n.push(i(n)+"rotate("+e+r)}(a.rotate,o.rotate,s,c),function(t,e,n,a){t!==e?a.push({i:n.push(i(n)+"skewX(",null,r)-2,x:yf(t,e)}):e&&n.push(i(n)+"skewX("+e+r)}(a.skewX,o.skewX,s,c),function(t,e,n,r,a,o){if(t!==n||e!==r){var s=a.push(i(a)+"scale(",null,",",null,")");o.push({i:s-4,x:yf(t,n)},{i:s-2,x:yf(e,r)})}else 1===n&&1===r||a.push(i(a)+"scale("+n+","+r+")")}(a.scaleX,a.scaleY,o.scaleX,o.scaleY,s,c),a=o=null,function(t){for(var e,n=-1,r=c.length;++n=1?(n=1,e-1):Math.floor(n*e),i=t[r],a=t[r+1],o=r>0?t[r-1]:2*i-a,s=ra&&(i=e.slice(a,i),s[o]?s[o]+=i:s[++o]=i),(n=n[0])===(r=r[0])?s[o]?s[o]+=r:s[++o]=r:(s[++o]=null,c.push({i:o,x:yf(n,r)})),a=Nf.lastIndex;return a=0&&(t=t.slice(0,e)),!t||"start"===t}))}(e)?ff:df;return function(){var o=a(this,t),s=o.on;s!==r&&(i=(r=s).copy()).on(e,n),o.on=i}}(n,t,e))},attr:function(t,e){var n=Gs(t),r="transform"===n?wf:Of;return this.attrTween(t,"function"==typeof e?(n.local?Pf:Ff)(n,r,Ef(this,"attr."+t,e)):null==e?(n.local?Lf:Bf)(n):(n.local?Rf:If)(n,r,e))},attrTween:function(t,e){var n="attr."+t;if(arguments.length<2)return(n=this.tween(n))&&n._value;if(null==e)return this.tween(n,null);if("function"!=typeof e)throw new Error;var r=Gs(t);return this.tween(n,(r.local?jf:Yf)(r,e))},style:function(t,e,n){var r="transform"==(t+="")?xf:Of;return null==e?this.styleTween(t,function(t,e){var n,r,i;return function(){var a=ac(this,t),o=(this.style.removeProperty(t),ac(this,t));return a===o?null:a===n&&o===r?i:i=e(n=a,r=o)}}(t,r)).on("end.style."+t,Wf(t)):"function"==typeof e?this.styleTween(t,function(t,e,n){var r,i,a;return function(){var o=ac(this,t),s=n(this),c=s+"";return null==s&&(this.style.removeProperty(t),c=s=ac(this,t)),o===c?null:o===r&&c===i?a:(i=c,a=e(r=o,s))}}(t,r,Ef(this,"style."+t,e))).each(function(t,e){var n,r,i,a,o="style."+e,s="end."+o;return function(){var c=df(this,t),u=c.on,l=null==c.value[o]?a||(a=Wf(e)):void 0;u===n&&i===l||(r=(n=u).copy()).on(s,i=l),c.on=r}}(this._id,t)):this.styleTween(t,function(t,e,n){var r,i,a=n+"";return function(){var o=ac(this,t);return o===a?null:o===r?i:i=e(r=o,n)}}(t,r,e),n).on("end.style."+t,null)},styleTween:function(t,e,n){var r="style."+(t+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(null==e)return this.tween(r,null);if("function"!=typeof e)throw new Error;return this.tween(r,function(t,e,n){var r,i;function a(){var a=e.apply(this,arguments);return a!==i&&(r=(i=a)&&function(t,e,n){return function(r){this.style.setProperty(t,e.call(this,r),n)}}(t,a,n)),r}return a._value=e,a}(t,e,null==n?"":n))},text:function(t){return this.tween("text","function"==typeof t?function(t){return function(){var e=t(this);this.textContent=null==e?"":e}}(Ef(this,"text",t)):function(t){return function(){this.textContent=t}}(null==t?"":t+""))},textTween:function(t){var e="text";if(arguments.length<1)return(e=this.tween(e))&&e._value;if(null==t)return this.tween(e,null);if("function"!=typeof t)throw new Error;return this.tween(e,function(t){var e,n;function r(){var r=t.apply(this,arguments);return r!==n&&(e=(n=r)&&function(t){return function(e){this.textContent=t.call(this,e)}}(r)),e}return r._value=t,r}(t))},remove:function(){return this.on("end.remove",function(t){return function(){var e=this.parentNode;for(var n in this.__transition)if(+n!==t)return;e&&e.removeChild(this)}}(this._id))},tween:function(t,e){var n=this._id;if(t+="",arguments.length<2){for(var r,i=pf(this.node(),n).tween,a=0,o=i.length;a2&&n.state<5,n.state=6,n.timer.stop(),n.on.call(r?"interrupt":"cancel",t,t.__data__,n.index,n.group),delete a[i]):o=!1;o&&delete t.__transition}}(this,t)}))},Yc.prototype.transition=function(t){var e,n;t instanceof Gf?(e=t._id,t=t._name):(e=Xf(),(n=Kf).time=tf(),t=null==t?null:t+"");for(var r=this._groups,i=r.length,a=0;ae?1:t>=e?0:NaN}ld.prototype={constructor:ld,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,e){return this._parent.insertBefore(t,e)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};var gd="http://www.w3.org/1999/xhtml";const md={svg:"http://www.w3.org/2000/svg",xhtml:gd,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};function vd(t){var e=t+="",n=e.indexOf(":");return n>=0&&"xmlns"!==(e=t.slice(0,n))&&(t=t.slice(n+1)),md.hasOwnProperty(e)?{space:md[e],local:t}:t}function bd(t){return function(){this.removeAttribute(t)}}function _d(t){return function(){this.removeAttributeNS(t.space,t.local)}}function xd(t,e){return function(){this.setAttribute(t,e)}}function wd(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function kd(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttribute(t):this.setAttribute(t,n)}}function Td(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,n)}}function Ed(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function Cd(t){return function(){this.style.removeProperty(t)}}function Sd(t,e,n){return function(){this.style.setProperty(t,e,n)}}function Ad(t,e,n){return function(){var r=e.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,n)}}function Md(t,e){return t.style.getPropertyValue(e)||Ed(t).getComputedStyle(t,null).getPropertyValue(e)}function Nd(t){return function(){delete this[t]}}function Dd(t,e){return function(){this[t]=e}}function Od(t,e){return function(){var n=e.apply(this,arguments);null==n?delete this[t]:this[t]=n}}function Bd(t){return t.trim().split(/^|\s+/)}function Ld(t){return t.classList||new Id(t)}function Id(t){this._node=t,this._names=Bd(t.getAttribute("class")||"")}function Rd(t,e){for(var n=Ld(t),r=-1,i=e.length;++r=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var sp=[null];function cp(t,e){this._groups=t,this._parents=e}function up(){return new cp([[document.documentElement]],sp)}cp.prototype=up.prototype={constructor:cp,select:function(t){"function"!=typeof t&&(t=td(t));for(var e=this._groups,n=e.length,r=new Array(n),i=0;i=_&&(_=b+1);!(v=g[_])&&++_=0;)(r=i[a])&&(o&&4^r.compareDocumentPosition(o)&&o.parentNode.insertBefore(r,o),o=r);return this},sort:function(t){function e(e,n){return e&&n?t(e.__data__,n.__data__):!e-!n}t||(t=yd);for(var n=this._groups,r=n.length,i=new Array(r),a=0;a1?this.each((null==e?Cd:"function"==typeof e?Ad:Sd)(t,e,null==n?"":n)):Md(this.node(),t)},property:function(t,e){return arguments.length>1?this.each((null==e?Nd:"function"==typeof e?Od:Dd)(t,e)):this.node()[t]},classed:function(t,e){var n=Bd(t+"");if(arguments.length<2){for(var r=Ld(this.node()),i=-1,a=n.length;++i=0&&(e=t.slice(n+1),t=t.slice(0,n)),{type:t,name:e}}))}(t+""),o=a.length;if(!(arguments.length<2)){for(s=e?rp:np,r=0;r{}};function fp(){for(var t,e=0,n=arguments.length,r={};e=0&&(n=t.slice(r+1),t=t.slice(0,r)),t&&!e.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))}(t+"",r),a=-1,o=i.length;if(!(arguments.length<2)){if(null!=e&&"function"!=typeof e)throw new Error("invalid callback: "+e);for(;++a0)for(var n,r,i=new Array(n),a=0;a=0&&e._call.call(void 0,t),e=e._next;--bp}()}finally{bp=0,function(){for(var t,e,n=mp,r=1/0;n;)n._call?(r>n._time&&(r=n._time),t=n,n=n._next):(e=n._next,n._next=null,n=t?t._next=e:mp=e);vp=t,Bp(r)}(),kp=0}}function Op(){var t=Ep.now(),e=t-wp;e>1e3&&(Tp-=e,wp=t)}function Bp(t){bp||(_p&&(_p=clearTimeout(_p)),t-kp>24?(t<1/0&&(_p=setTimeout(Dp,t-Ep.now()-Tp)),xp&&(xp=clearInterval(xp))):(xp||(wp=Ep.now(),xp=setInterval(Op,1e3)),bp=1,Cp(Dp)))}function Lp(t,e,n){var r=new Mp;return e=null==e?0:+e,r.restart((n=>{r.stop(),t(n+e)}),e,n),r}Mp.prototype=Np.prototype={constructor:Mp,restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?Sp():+n)+(null==e?0:+e),this._next||vp===this||(vp?vp._next=this:mp=this,vp=this),this._call=t,this._time=n,Bp()},stop:function(){this._call&&(this._call=null,this._time=1/0,Bp())}};var Ip=gp("start","end","cancel","interrupt"),Rp=[];function Fp(t,e,n,r,i,a){var o=t.__transition;if(o){if(n in o)return}else t.__transition={};!function(t,e,n){var r,i=t.__transition;function a(c){var u,l,h,f;if(1!==n.state)return s();for(u in i)if((f=i[u]).name===n.name){if(3===f.state)return Lp(a);4===f.state?(f.state=6,f.timer.stop(),f.on.call("interrupt",t,t.__data__,f.index,f.group),delete i[u]):+u0)throw new Error("too late; already scheduled");return n}function jp(t,e){var n=Yp(t,e);if(n.state>3)throw new Error("too late; already running");return n}function Yp(t,e){var n=t.__transition;if(!n||!(n=n[e]))throw new Error("transition not found");return n}function zp(t,e){return t=+t,e=+e,function(n){return t*(1-n)+e*n}}var Up,qp=180/Math.PI,Hp={translateX:0,translateY:0,rotate:0,skewX:0,scaleX:1,scaleY:1};function $p(t,e,n,r,i,a){var o,s,c;return(o=Math.sqrt(t*t+e*e))&&(t/=o,e/=o),(c=t*n+e*r)&&(n-=t*c,r-=e*c),(s=Math.sqrt(n*n+r*r))&&(n/=s,r/=s,c/=s),t*r180?e+=360:e-t>180&&(t+=360),a.push({i:n.push(i(n)+"rotate(",null,r)-2,x:zp(t,e)})):e&&n.push(i(n)+"rotate("+e+r)}(a.rotate,o.rotate,s,c),function(t,e,n,a){t!==e?a.push({i:n.push(i(n)+"skewX(",null,r)-2,x:zp(t,e)}):e&&n.push(i(n)+"skewX("+e+r)}(a.skewX,o.skewX,s,c),function(t,e,n,r,a,o){if(t!==n||e!==r){var s=a.push(i(a)+"scale(",null,",",null,")");o.push({i:s-4,x:zp(t,n)},{i:s-2,x:zp(e,r)})}else 1===n&&1===r||a.push(i(a)+"scale("+n+","+r+")")}(a.scaleX,a.scaleY,o.scaleX,o.scaleY,s,c),a=o=null,function(t){for(var e,n=-1,r=c.length;++n>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===n?vy(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===n?vy(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=sy.exec(t))?new _y(e[1],e[2],e[3],1):(e=cy.exec(t))?new _y(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=uy.exec(t))?vy(e[1],e[2],e[3],e[4]):(e=ly.exec(t))?vy(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=hy.exec(t))?Ty(e[1],e[2]/100,e[3]/100,1):(e=fy.exec(t))?Ty(e[1],e[2]/100,e[3]/100,e[4]):dy.hasOwnProperty(t)?my(dy[t]):"transparent"===t?new _y(NaN,NaN,NaN,0):null}function my(t){return new _y(t>>16&255,t>>8&255,255&t,1)}function vy(t,e,n,r){return r<=0&&(t=e=n=NaN),new _y(t,e,n,r)}function by(t,e,n,r){return 1===arguments.length?function(t){return t instanceof ty||(t=gy(t)),t?new _y((t=t.rgb()).r,t.g,t.b,t.opacity):new _y}(t):new _y(t,e,n,null==r?1:r)}function _y(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}function xy(){return"#"+ky(this.r)+ky(this.g)+ky(this.b)}function wy(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function ky(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function Ty(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new Cy(t,e,n,r)}function Ey(t){if(t instanceof Cy)return new Cy(t.h,t.s,t.l,t.opacity);if(t instanceof ty||(t=gy(t)),!t)return new Cy;if(t instanceof Cy)return t;var e=(t=t.rgb()).r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),a=Math.max(e,n,r),o=NaN,s=a-i,c=(a+i)/2;return s?(o=e===a?(n-r)/s+6*(n0&&c<1?0:o,new Cy(o,s,c,t.opacity)}function Cy(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}function Sy(t,e,n){return 255*(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)}function Ay(t,e,n,r,i){var a=t*t,o=a*t;return((1-3*t+3*a-o)*e+(4-6*a+3*o)*n+(1+3*t+3*a-3*o)*r+o*i)/6}Qp(ty,gy,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:py,formatHex:py,formatHsl:function(){return Ey(this).formatHsl()},formatRgb:yy,toString:yy}),Qp(_y,by,Jp(ty,{brighter:function(t){return t=null==t?ny:Math.pow(ny,t),new _y(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?ey:Math.pow(ey,t),new _y(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:xy,formatHex:xy,formatRgb:wy,toString:wy})),Qp(Cy,(function(t,e,n,r){return 1===arguments.length?Ey(t):new Cy(t,e,n,null==r?1:r)}),Jp(ty,{brighter:function(t){return t=null==t?ny:Math.pow(ny,t),new Cy(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?ey:Math.pow(ey,t),new Cy(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new _y(Sy(t>=240?t-240:t+120,i,r),Sy(t,i,r),Sy(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));const My=t=>()=>t;function Ny(t,e){var n=e-t;return n?function(t,e){return function(n){return t+n*e}}(t,n):My(isNaN(t)?e:t)}const Dy=function t(e){var n=function(t){return 1==(t=+t)?Ny:function(e,n){return n-e?function(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(r){return Math.pow(t+r*e,n)}}(e,n,t):My(isNaN(e)?n:e)}}(e);function r(t,e){var r=n((t=by(t)).r,(e=by(e)).r),i=n(t.g,e.g),a=n(t.b,e.b),o=Ny(t.opacity,e.opacity);return function(e){return t.r=r(e),t.g=i(e),t.b=a(e),t.opacity=o(e),t+""}}return r.gamma=t,r}(1);function Oy(t){return function(e){var n,r,i=e.length,a=new Array(i),o=new Array(i),s=new Array(i);for(n=0;n=1?(n=1,e-1):Math.floor(n*e),i=t[r],a=t[r+1],o=r>0?t[r-1]:2*i-a,s=ra&&(i=e.slice(a,i),s[o]?s[o]+=i:s[++o]=i),(n=n[0])===(r=r[0])?s[o]?s[o]+=r:s[++o]=r:(s[++o]=null,c.push({i:o,x:zp(n,r)})),a=Ly.lastIndex;return a=0&&(t=t.slice(0,e)),!t||"start"===t}))}(e)?Pp:jp;return function(){var o=a(this,t),s=o.on;s!==r&&(i=(r=s).copy()).on(e,n),o.on=i}}(n,t,e))},attr:function(t,e){var n=vd(t),r="transform"===n?Gp:Ry;return this.attrTween(t,"function"==typeof e?(n.local?Uy:zy)(n,r,Kp(this,"attr."+t,e)):null==e?(n.local?Py:Fy)(n):(n.local?Yy:jy)(n,r,e))},attrTween:function(t,e){var n="attr."+t;if(arguments.length<2)return(n=this.tween(n))&&n._value;if(null==e)return this.tween(n,null);if("function"!=typeof e)throw new Error;var r=vd(t);return this.tween(n,(r.local?qy:Hy)(r,e))},style:function(t,e,n){var r="transform"==(t+="")?Vp:Ry;return null==e?this.styleTween(t,function(t,e){var n,r,i;return function(){var a=Md(this,t),o=(this.style.removeProperty(t),Md(this,t));return a===o?null:a===n&&o===r?i:i=e(n=a,r=o)}}(t,r)).on("end.style."+t,Zy(t)):"function"==typeof e?this.styleTween(t,function(t,e,n){var r,i,a;return function(){var o=Md(this,t),s=n(this),c=s+"";return null==s&&(this.style.removeProperty(t),c=s=Md(this,t)),o===c?null:o===r&&c===i?a:(i=c,a=e(r=o,s))}}(t,r,Kp(this,"style."+t,e))).each(function(t,e){var n,r,i,a,o="style."+e,s="end."+o;return function(){var c=jp(this,t),u=c.on,l=null==c.value[o]?a||(a=Zy(e)):void 0;u===n&&i===l||(r=(n=u).copy()).on(s,i=l),c.on=r}}(this._id,t)):this.styleTween(t,function(t,e,n){var r,i,a=n+"";return function(){var o=Md(this,t);return o===a?null:o===r?i:i=e(r=o,n)}}(t,r,e),n).on("end.style."+t,null)},styleTween:function(t,e,n){var r="style."+(t+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(null==e)return this.tween(r,null);if("function"!=typeof e)throw new Error;return this.tween(r,function(t,e,n){var r,i;function a(){var a=e.apply(this,arguments);return a!==i&&(r=(i=a)&&function(t,e,n){return function(r){this.style.setProperty(t,e.call(this,r),n)}}(t,a,n)),r}return a._value=e,a}(t,e,null==n?"":n))},text:function(t){return this.tween("text","function"==typeof t?function(t){return function(){var e=t(this);this.textContent=null==e?"":e}}(Kp(this,"text",t)):function(t){return function(){this.textContent=t}}(null==t?"":t+""))},textTween:function(t){var e="text";if(arguments.length<1)return(e=this.tween(e))&&e._value;if(null==t)return this.tween(e,null);if("function"!=typeof t)throw new Error;return this.tween(e,function(t){var e,n;function r(){var r=t.apply(this,arguments);return r!==n&&(e=(n=r)&&function(t){return function(e){this.textContent=t.call(this,e)}}(r)),e}return r._value=t,r}(t))},remove:function(){return this.on("end.remove",function(t){return function(){var e=this.parentNode;for(var n in this.__transition)if(+n!==t)return;e&&e.removeChild(this)}}(this._id))},tween:function(t,e){var n=this._id;if(t+="",arguments.length<2){for(var r,i=Yp(this.node(),n).tween,a=0,o=i.length;a2&&n.state<5,n.state=6,n.timer.stop(),n.on.call(r?"interrupt":"cancel",t,t.__data__,n.index,n.group),delete a[i]):o=!1;o&&delete t.__transition}}(this,t)}))},lp.prototype.transition=function(t){var e,n;t instanceof Qy?(e=t._id,t=t._name):(e=Jy(),(n=eg).time=Sp(),t=null==t?null:t+"");for(var r=this._groups,i=r.length,a=0;a2||Ng(kg)>3?"":" "}function Bg(t,e){for(;--e&&Cg()&&!(kg<48||kg>102||kg>57&&kg<65||kg>70&&kg<97););return Mg(t,Ag()+(e<6&&32==Sg()&&32==Cg()))}function Lg(t){for(;Cg();)switch(kg){case t:return wg;case 34:case 39:34!==t&&39!==t&&Lg(kg);break;case 40:41===t&&Lg(t);break;case 92:Cg()}return wg}function Ig(t,e){for(;Cg()&&t+kg!==57&&(t+kg!==84||47!==Sg()););return"/*"+Mg(e,wg-1)+"*"+cg(47===t?t:Cg())}function Rg(t){for(;!Ng(Sg());)Cg();return Mg(t,wg)}function Fg(t){return function(t){return Tg="",t}(Pg("",null,null,null,[""],t=function(t){return bg=_g=1,xg=pg(Tg=t),wg=0,[]}(t),0,[0],t))}function Pg(t,e,n,r,i,a,o,s,c){for(var u=0,l=0,h=o,f=0,d=0,p=0,y=1,g=1,m=1,v=0,b="",_=i,x=a,w=r,k=b;g;)switch(p=v,v=Cg()){case 40:if(108!=p&&58==k.charCodeAt(h-1)){-1!=hg(k+=lg(Dg(v),"&","&\f"),"&\f")&&(m=-1);break}case 34:case 39:case 91:k+=Dg(v);break;case 9:case 10:case 13:case 32:k+=Og(p);break;case 92:k+=Bg(Ag()-1,7);continue;case 47:switch(Sg()){case 42:case 47:gg(Yg(Ig(Cg(),Ag()),e,n),c);break;default:k+="/"}break;case 123*y:s[u++]=pg(k)*m;case 125*y:case 59:case 0:switch(v){case 0:case 125:g=0;case 59+l:d>0&&pg(k)-h&&gg(d>32?zg(k+";",r,n,h-1):zg(lg(k," ","")+";",r,n,h-2),c);break;case 59:k+=";";default:if(gg(w=jg(k,e,n,u,l,i,s,b,_=[],x=[],h),a),123===v)if(0===l)Pg(k,e,w,w,_,a,h,s,x);else switch(f){case 100:case 109:case 115:Pg(t,w,w,r&&gg(jg(t,w,w,0,0,i,s,b,i,_=[],h),x),i,x,h,s,r?_:x);break;default:Pg(k,w,w,w,[""],x,0,s,x)}}u=l=d=0,y=m=1,b=k="",h=o;break;case 58:h=1+pg(k),d=p;default:if(y<1)if(123==v)--y;else if(125==v&&0==y++&&125==(kg=wg>0?fg(Tg,--wg):0,_g--,10===kg&&(_g=1,bg--),kg))continue;switch(k+=cg(v),v*y){case 38:m=l>0?1:(k+="\f",-1);break;case 44:s[u++]=(pg(k)-1)*m,m=1;break;case 64:45===Sg()&&(k+=Dg(Cg())),f=Sg(),l=h=pg(b=k+=Rg(Ag())),v++;break;case 45:45===p&&2==pg(k)&&(y=0)}}return a}function jg(t,e,n,r,i,a,o,s,c,u,l){for(var h=i-1,f=0===i?a:[""],d=yg(f),p=0,y=0,g=0;p0?f[m]+" "+v:lg(v,/&\f/g,f[m])))&&(c[g++]=b);return Eg(t,e,n,0===i?ag:s,c,u,l)}function Yg(t,e,n){return Eg(t,e,n,ig,cg(kg),dg(t,2,-2),0)}function zg(t,e,n,r){return Eg(t,e,n,og,dg(t,0,r),dg(t,r+1,-1),r)}const Ug="8.14.0";var qg=n(9609),Hg=n(7856),$g=n.n(Hg),Wg=function(t){var e=t.replace(/\\u[\dA-F]{4}/gi,(function(t){return String.fromCharCode(parseInt(t.replace(/\\u/g,""),16))}));return(e=(e=e.replace(/\\x([0-9a-f]{2})/gi,(function(t,e){return String.fromCharCode(parseInt(e,16))}))).replace(/\\[\d\d\d]{3}/gi,(function(t){return String.fromCharCode(parseInt(t.replace(/\\/g,""),8))}))).replace(/\\[\d\d\d]{2}/gi,(function(t){return String.fromCharCode(parseInt(t.replace(/\\/g,""),8))}))},Vg=function(t){for(var e="",n=0;n>=0;){if(!((n=t.indexOf("=0)){e+=t,n=-1;break}e+=t.substr(0,n),(n=(t=t.substr(n+1)).indexOf("<\/script>"))>=0&&(n+=9,t=t.substr(n))}var r=Wg(e);return(r=(r=(r=r.replace(/script>/gi,"#")).replace(/javascript:/gi,"#")).replace(/onerror=/gi,"onerror:")).replace(/')}if(void 0!==n)switch(y){case"flowchart":case"flowchart-v2":n(T,f_.bindFunctions);break;case"gantt":n(T,px.bindFunctions);break;case"class":case"classDiagram":n(T,wv.bindFunctions);break;default:n(T)}else o.debug("CB = undefined!");Gw.forEach((function(t){t()})),Gw=[];var S="sandbox"===s.securityLevel?"#i"+t:"#d"+t,A=zc(S).node();return null!==A&&"function"==typeof A.remove&&zc(S).node().remove(),T},parse:function(t){var e=rv(),n=Gm.detectInit(t,e);n&&o.debug("reinit ",n);var r,i=Gm.detectType(t,e);switch(o.debug("Type "+i),i){case"git":(r=Ix()).parser.yy=Ox;break;case"flowchart":case"flowchart-v2":f_.clear(),(r=p_()).parser.yy=f_;break;case"sequence":(r=Cw()).parser.yy=Vw;break;case"gantt":(r=vx()).parser.yy=px;break;case"class":case"classDiagram":(r=Av()).parser.yy=wv;break;case"state":case"stateDiagram":(r=Tk()).parser.yy=jk;break;case"info":o.debug("info info info"),(r=Zx()).parser.yy=Gx;break;case"pie":o.debug("pie"),(r=tw()).parser.yy=iw;break;case"er":o.debug("er"),(r=Lb()).parser.yy=Ob;break;case"journey":o.debug("Journey"),(r=fT()).parser.yy=lT;break;case"requirement":case"requirementDiagram":o.debug("RequirementDiagram"),(r=uw()).parser.yy=yw}return r.parser.yy.graphType=i,r.parser.yy.parseError=function(t,e){throw{str:t,hash:e}},r.parse(t),r},parseDirective:function(t,e,n,r){try{if(void 0!==e)switch(e=e.trim(),n){case"open_directive":LT={};break;case"type_directive":LT.type=e.toLowerCase();break;case"arg_directive":LT.args=JSON.parse(e);break;case"close_directive":(function(t,e,n){switch(o.debug("Directive type=".concat(e.type," with args:"),e.args),e.type){case"init":case"initialize":["config"].forEach((function(t){void 0!==e.args[t]&&("flowchart-v2"===n&&(n="flowchart"),e.args[n]=e.args[t],delete e.args[t])})),o.debug("sanitize in handleDirective",e.args),Wm(e.args),o.debug("sanitize in handleDirective (done)",e.args),e.args,av(e.args);break;case"wrap":case"nowrap":t&&t.setWrap&&t.setWrap("wrap"===e.type);break;case"themeCss":o.warn("themeCss encountered");break;default:o.warn("Unhandled directive: source: '%%{".concat(e.type,": ").concat(JSON.stringify(e.args?e.args:{}),"}%%"),e)}})(t,LT,r),LT=null}}catch(t){o.error("Error while rendering sequenceDiagram directive: ".concat(e," jison context: ").concat(n)),o.error(t.message)}},initialize:function(t){t&&t.fontFamily&&(t.themeVariables&&t.themeVariables.fontFamily||(t.themeVariables={fontFamily:t.fontFamily})),function(t){Zm=Pm({},t)}(t),t&&t.theme&&fm[t.theme]?t.themeVariables=fm[t.theme].getThemeVariables(t.themeVariables):t&&(t.themeVariables=fm.default.getThemeVariables(t.themeVariables));var e="object"===OT(t)?function(t){return Qm=Pm({},Km),Qm=Pm(Qm,t),t.theme&&(Qm.themeVariables=fm[t.theme].getThemeVariables(t.themeVariables)),tv=ev(Qm,Jm),Qm}(t):nv();IT(e),s(e.logLevel)},reinitialize:function(){},getConfig:rv,setConfig:function(t){return Pm(tv,t),rv()},getSiteConfig:nv,updateSiteConfig:function(t){return Qm=Pm(Qm,t),ev(Qm,Jm),Qm},reset:function(){ov()},globalReset:function(){ov(),IT(rv())},defaultConfig:Km});s(rv().logLevel),ov(rv());const FT=RT;var PT=function(){jT.startOnLoad?FT.getConfig().startOnLoad&&jT.init():void 0===jT.startOnLoad&&(o.debug("In start, no config"),FT.getConfig().startOnLoad&&jT.init())};"undefined"!=typeof document&&window.addEventListener("load",(function(){PT()}),!1);var jT={startOnLoad:!0,htmlLabels:!0,mermaidAPI:FT,parse:FT.parse,render:FT.render,init:function(){var t,e,n=this,r=FT.getConfig();arguments.length>=2?(void 0!==arguments[0]&&(jT.sequenceConfig=arguments[0]),t=arguments[1]):t=arguments[0],"function"==typeof arguments[arguments.length-1]?(e=arguments[arguments.length-1],o.debug("Callback function found")):void 0!==r.mermaid&&("function"==typeof r.mermaid.callback?(e=r.mermaid.callback,o.debug("Callback function found")):o.debug("No Callback function found")),t=void 0===t?document.querySelectorAll(".mermaid"):"string"==typeof t?document.querySelectorAll(t):t instanceof window.Node?[t]:t,o.debug("Start On Load before: "+jT.startOnLoad),void 0!==jT.startOnLoad&&(o.debug("Start On Load inner: "+jT.startOnLoad),FT.updateSiteConfig({startOnLoad:jT.startOnLoad})),void 0!==jT.ganttConfig&&FT.updateSiteConfig({gantt:jT.ganttConfig});for(var i,a=new Gm.initIdGeneratior(r.deterministicIds,r.deterministicIDSeed),s=function(r){var s=t[r];if(s.getAttribute("data-processed"))return"continue";s.setAttribute("data-processed",!0);var c="mermaid-".concat(a.next());i=s.innerHTML,i=Gm.entityDecode(i).trim().replace(//gi,"
");var u=Gm.detectInit(i);u&&o.debug("Detected early reinit: ",u);try{FT.render(c,i,(function(t,n){s.innerHTML=t,void 0!==e&&e(c),n&&n(s)}),s)}catch(t){o.warn("Syntax Error rendering"),o.warn(t),n.parseError&&n.parseError(t)}},c=0;c{t.exports={graphlib:n(6614),dagre:n(1463),intersect:n(8114),render:n(5787),util:n(8355),version:n(5689)}},9144:(t,e,n)=>{var r=n(8355);function i(t,e,n,i){var a=t.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z").style("stroke-width",1).style("stroke-dasharray","1,0");r.applyStyle(a,n[i+"Style"]),n[i+"Class"]&&a.attr("class",n[i+"Class"])}t.exports={default:i,normal:i,vee:function(t,e,n,i){var a=t.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 L 4 5 z").style("stroke-width",1).style("stroke-dasharray","1,0");r.applyStyle(a,n[i+"Style"]),n[i+"Class"]&&a.attr("class",n[i+"Class"])},undirected:function(t,e,n,i){var a=t.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M 0 5 L 10 5").style("stroke-width",1).style("stroke-dasharray","1,0");r.applyStyle(a,n[i+"Style"]),n[i+"Class"]&&a.attr("class",n[i+"Class"])}}},5632:(t,e,n)=>{var r=n(8355),i=n(4322),a=n(1322);t.exports=function(t,e){var n,o=e.nodes().filter((function(t){return r.isSubgraph(e,t)})),s=t.selectAll("g.cluster").data(o,(function(t){return t}));return s.selectAll("*").remove(),s.enter().append("g").attr("class","cluster").attr("id",(function(t){return e.node(t).id})).style("opacity",0),s=t.selectAll("g.cluster"),r.applyTransition(s,e).style("opacity",1),s.each((function(t){var n=e.node(t),r=i.select(this);i.select(this).append("rect");var o=r.append("g").attr("class","label");a(o,n,n.clusterLabelPos)})),s.selectAll("rect").each((function(t){var n=e.node(t),a=i.select(this);r.applyStyle(a,n.style)})),n=s.exit?s.exit():s.selectAll(null),r.applyTransition(n,e).style("opacity",0).remove(),s}},6315:(t,e,n)=>{var r=n(1034),i=n(1322),a=n(8355),o=n(4322);t.exports=function(t,e){var n,s=t.selectAll("g.edgeLabel").data(e.edges(),(function(t){return a.edgeToId(t)})).classed("update",!0);return s.exit().remove(),s.enter().append("g").classed("edgeLabel",!0).style("opacity",0),(s=t.selectAll("g.edgeLabel")).each((function(t){var n=o.select(this);n.select(".label").remove();var a=e.edge(t),s=i(n,e.edge(t),0,0).classed("label",!0),c=s.node().getBBox();a.labelId&&s.attr("id",a.labelId),r.has(a,"width")||(a.width=c.width),r.has(a,"height")||(a.height=c.height)})),n=s.exit?s.exit():s.selectAll(null),a.applyTransition(n,e).style("opacity",0).remove(),s}},940:(t,e,n)=>{var r=n(1034),i=n(7584),a=n(8355),o=n(4322);function s(t,e){var n=(o.line||o.svg.line)().x((function(t){return t.x})).y((function(t){return t.y}));return(n.curve||n.interpolate)(t.curve),n(e)}t.exports=function(t,e,n){var c=t.selectAll("g.edgePath").data(e.edges(),(function(t){return a.edgeToId(t)})).classed("update",!0),u=function(t,e){var n=t.enter().append("g").attr("class","edgePath").style("opacity",0);return n.append("path").attr("class","path").attr("d",(function(t){var n=e.edge(t),i=e.node(t.v).elem;return s(n,r.range(n.points.length).map((function(){return e=(t=i).getBBox(),{x:(n=t.ownerSVGElement.getScreenCTM().inverse().multiply(t.getScreenCTM()).translate(e.width/2,e.height/2)).e,y:n.f};var t,e,n})))})),n.append("defs"),n}(c,e);!function(t,e){var n=t.exit();a.applyTransition(n,e).style("opacity",0).remove()}(c,e);var l=void 0!==c.merge?c.merge(u):c;return a.applyTransition(l,e).style("opacity",1),l.each((function(t){var n=o.select(this),r=e.edge(t);r.elem=this,r.id&&n.attr("id",r.id),a.applyClass(n,r.class,(n.classed("update")?"update ":"")+"edgePath")})),l.selectAll("path.path").each((function(t){var n=e.edge(t);n.arrowheadId=r.uniqueId("arrowhead");var c=o.select(this).attr("marker-end",(function(){return"url("+(t=location.href,e=n.arrowheadId,t.split("#")[0]+"#"+e+")");var t,e})).style("fill","none");a.applyTransition(c,e).attr("d",(function(t){return function(t,e){var n=t.edge(e),r=t.node(e.v),a=t.node(e.w),o=n.points.slice(1,n.points.length-1);return o.unshift(i(r,o[0])),o.push(i(a,o[o.length-1])),s(n,o)}(e,t)})),a.applyStyle(c,n.style)})),l.selectAll("defs *").remove(),l.selectAll("defs").each((function(t){var r=e.edge(t);(0,n[r.arrowhead])(o.select(this),r.arrowheadId,r,"arrowhead")})),l}},607:(t,e,n)=>{var r=n(1034),i=n(1322),a=n(8355),o=n(4322);t.exports=function(t,e,n){var s,c=e.nodes().filter((function(t){return!a.isSubgraph(e,t)})),u=t.selectAll("g.node").data(c,(function(t){return t})).classed("update",!0);return u.exit().remove(),u.enter().append("g").attr("class","node").style("opacity",0),(u=t.selectAll("g.node")).each((function(t){var s=e.node(t),c=o.select(this);a.applyClass(c,s.class,(c.classed("update")?"update ":"")+"node"),c.select("g.label").remove();var u=c.append("g").attr("class","label"),l=i(u,s),h=n[s.shape],f=r.pick(l.node().getBBox(),"width","height");s.elem=this,s.id&&c.attr("id",s.id),s.labelId&&u.attr("id",s.labelId),r.has(s,"width")&&(f.width=s.width),r.has(s,"height")&&(f.height=s.height),f.width+=s.paddingLeft+s.paddingRight,f.height+=s.paddingTop+s.paddingBottom,u.attr("transform","translate("+(s.paddingLeft-s.paddingRight)/2+","+(s.paddingTop-s.paddingBottom)/2+")");var d=o.select(this);d.select(".label-container").remove();var p=h(d,f,s).classed("label-container",!0);a.applyStyle(p,s.style);var y=p.node().getBBox();s.width=y.width,s.height=y.height})),s=u.exit?u.exit():u.selectAll(null),a.applyTransition(s,e).style("opacity",0).remove(),u}},4322:(t,e,n)=>{var r;if(!r)try{r=n(7188)}catch(t){}r||(r=window.d3),t.exports=r},1463:(t,e,n)=>{var r;try{r=n(681)}catch(t){}r||(r=window.dagre),t.exports=r},6614:(t,e,n)=>{var r;try{r=n(8282)}catch(t){}r||(r=window.graphlib),t.exports=r},8114:(t,e,n)=>{t.exports={node:n(7584),circle:n(6587),ellipse:n(3260),polygon:n(5337),rect:n(8049)}},6587:(t,e,n)=>{var r=n(3260);t.exports=function(t,e,n){return r(t,e,e,n)}},3260:t=>{t.exports=function(t,e,n,r){var i=t.x,a=t.y,o=i-r.x,s=a-r.y,c=Math.sqrt(e*e*s*s+n*n*o*o),u=Math.abs(e*n*o/c);r.x{function e(t,e){return t*e>0}t.exports=function(t,n,r,i){var a,o,s,c,u,l,h,f,d,p,y,g,m;if(a=n.y-t.y,s=t.x-n.x,u=n.x*t.y-t.x*n.y,d=a*r.x+s*r.y+u,p=a*i.x+s*i.y+u,!(0!==d&&0!==p&&e(d,p)||(o=i.y-r.y,c=r.x-i.x,l=i.x*r.y-r.x*i.y,h=o*t.x+c*t.y+l,f=o*n.x+c*n.y+l,0!==h&&0!==f&&e(h,f)||0==(y=a*c-o*s))))return g=Math.abs(y/2),{x:(m=s*l-c*u)<0?(m-g)/y:(m+g)/y,y:(m=o*u-a*l)<0?(m-g)/y:(m+g)/y}}},7584:t=>{t.exports=function(t,e){return t.intersect(e)}},5337:(t,e,n)=>{var r=n(6808);t.exports=function(t,e,n){var i=t.x,a=t.y,o=[],s=Number.POSITIVE_INFINITY,c=Number.POSITIVE_INFINITY;e.forEach((function(t){s=Math.min(s,t.x),c=Math.min(c,t.y)}));for(var u=i-t.width/2-s,l=a-t.height/2-c,h=0;h1&&o.sort((function(t,e){var r=t.x-n.x,i=t.y-n.y,a=Math.sqrt(r*r+i*i),o=e.x-n.x,s=e.y-n.y,c=Math.sqrt(o*o+s*s);return a{t.exports=function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,c=t.width/2,u=t.height/2;return Math.abs(s)*c>Math.abs(o)*u?(s<0&&(u=-u),n=0===s?0:u*o/s,r=u):(o<0&&(c=-c),n=c,r=0===o?0:c*s/o),{x:i+n,y:a+r}}},8284:(t,e,n)=>{var r=n(8355);t.exports=function(t,e){var n=t.append("foreignObject").attr("width","100000"),i=n.append("xhtml:div");i.attr("xmlns","http://www.w3.org/1999/xhtml");var a=e.label;switch(typeof a){case"function":i.insert(a);break;case"object":i.insert((function(){return a}));break;default:i.html(a)}r.applyStyle(i,e.labelStyle),i.style("display","inline-block"),i.style("white-space","nowrap");var o=i.node().getBoundingClientRect();return n.attr("width",o.width).attr("height",o.height),n}},1322:(t,e,n)=>{var r=n(7318),i=n(8284),a=n(8287);t.exports=function(t,e,n){var o=e.label,s=t.append("g");"svg"===e.labelType?a(s,e):"string"!=typeof o||"html"===e.labelType?i(s,e):r(s,e);var c,u=s.node().getBBox();switch(n){case"top":c=-e.height/2;break;case"bottom":c=e.height/2-u.height;break;default:c=-u.height/2}return s.attr("transform","translate("+-u.width/2+","+c+")"),s}},8287:(t,e,n)=>{var r=n(8355);t.exports=function(t,e){var n=t;return n.node().appendChild(e.label),r.applyStyle(n,e.labelStyle),n}},7318:(t,e,n)=>{var r=n(8355);t.exports=function(t,e){for(var n=t.append("text"),i=function(t){for(var e,n="",r=!1,i=0;i{var r;try{r={defaults:n(1747),each:n(6073),isFunction:n(3560),isPlainObject:n(8630),pick:n(9722),has:n(8721),range:n(6026),uniqueId:n(3955)}}catch(t){}r||(r=window._),t.exports=r},6381:(t,e,n)=>{var r=n(8355),i=n(4322);t.exports=function(t,e){var n=t.filter((function(){return!i.select(this).classed("update")}));function a(t){var n=e.node(t);return"translate("+n.x+","+n.y+")"}n.attr("transform",a),r.applyTransition(t,e).style("opacity",1).attr("transform",a),r.applyTransition(n.selectAll("rect"),e).attr("width",(function(t){return e.node(t).width})).attr("height",(function(t){return e.node(t).height})).attr("x",(function(t){return-e.node(t).width/2})).attr("y",(function(t){return-e.node(t).height/2}))}},4577:(t,e,n)=>{var r=n(8355),i=n(4322),a=n(1034);t.exports=function(t,e){function n(t){var n=e.edge(t);return a.has(n,"x")?"translate("+n.x+","+n.y+")":""}t.filter((function(){return!i.select(this).classed("update")})).attr("transform",n),r.applyTransition(t,e).style("opacity",1).attr("transform",n)}},4849:(t,e,n)=>{var r=n(8355),i=n(4322);t.exports=function(t,e){function n(t){var n=e.node(t);return"translate("+n.x+","+n.y+")"}t.filter((function(){return!i.select(this).classed("update")})).attr("transform",n),r.applyTransition(t,e).style("opacity",1).attr("transform",n)}},5787:(t,e,n)=>{var r=n(1034),i=n(4322),a=n(1463).layout;t.exports=function(){var t=n(607),e=n(5632),i=n(6315),u=n(940),l=n(4849),h=n(4577),f=n(6381),d=n(4418),p=n(9144),y=function(n,y){!function(t){t.nodes().forEach((function(e){var n=t.node(e);r.has(n,"label")||t.children(e).length||(n.label=e),r.has(n,"paddingX")&&r.defaults(n,{paddingLeft:n.paddingX,paddingRight:n.paddingX}),r.has(n,"paddingY")&&r.defaults(n,{paddingTop:n.paddingY,paddingBottom:n.paddingY}),r.has(n,"padding")&&r.defaults(n,{paddingLeft:n.padding,paddingRight:n.padding,paddingTop:n.padding,paddingBottom:n.padding}),r.defaults(n,o),r.each(["paddingLeft","paddingRight","paddingTop","paddingBottom"],(function(t){n[t]=Number(n[t])})),r.has(n,"width")&&(n._prevWidth=n.width),r.has(n,"height")&&(n._prevHeight=n.height)})),t.edges().forEach((function(e){var n=t.edge(e);r.has(n,"label")||(n.label=""),r.defaults(n,s)}))}(y);var g=c(n,"output"),m=c(g,"clusters"),v=c(g,"edgePaths"),b=i(c(g,"edgeLabels"),y),_=t(c(g,"nodes"),y,d);a(y),l(_,y),h(b,y),u(v,y,p);var x=e(m,y);f(x,y),function(t){r.each(t.nodes(),(function(e){var n=t.node(e);r.has(n,"_prevWidth")?n.width=n._prevWidth:delete n.width,r.has(n,"_prevHeight")?n.height=n._prevHeight:delete n.height,delete n._prevWidth,delete n._prevHeight}))}(y)};return y.createNodes=function(e){return arguments.length?(t=e,y):t},y.createClusters=function(t){return arguments.length?(e=t,y):e},y.createEdgeLabels=function(t){return arguments.length?(i=t,y):i},y.createEdgePaths=function(t){return arguments.length?(u=t,y):u},y.shapes=function(t){return arguments.length?(d=t,y):d},y.arrows=function(t){return arguments.length?(p=t,y):p},y};var o={paddingLeft:10,paddingRight:10,paddingTop:10,paddingBottom:10,rx:0,ry:0,shape:"rect"},s={arrowhead:"normal",curve:i.curveLinear};function c(t,e){var n=t.select("g."+e);return n.empty()&&(n=t.append("g").attr("class",e)),n}},4418:(t,e,n)=>{var r=n(8049),i=n(3260),a=n(6587),o=n(5337);t.exports={rect:function(t,e,n){var i=t.insert("rect",":first-child").attr("rx",n.rx).attr("ry",n.ry).attr("x",-e.width/2).attr("y",-e.height/2).attr("width",e.width).attr("height",e.height);return n.intersect=function(t){return r(n,t)},i},ellipse:function(t,e,n){var r=e.width/2,a=e.height/2,o=t.insert("ellipse",":first-child").attr("x",-e.width/2).attr("y",-e.height/2).attr("rx",r).attr("ry",a);return n.intersect=function(t){return i(n,r,a,t)},o},circle:function(t,e,n){var r=Math.max(e.width,e.height)/2,i=t.insert("circle",":first-child").attr("x",-e.width/2).attr("y",-e.height/2).attr("r",r);return n.intersect=function(t){return a(n,r,t)},i},diamond:function(t,e,n){var r=e.width*Math.SQRT2/2,i=e.height*Math.SQRT2/2,a=[{x:0,y:-i},{x:-r,y:0},{x:0,y:i},{x:r,y:0}],s=t.insert("polygon",":first-child").attr("points",a.map((function(t){return t.x+","+t.y})).join(" "));return n.intersect=function(t){return o(n,a,t)},s}}},8355:(t,e,n)=>{var r=n(1034);t.exports={isSubgraph:function(t,e){return!!t.children(e).length},edgeToId:function(t){return a(t.v)+":"+a(t.w)+":"+a(t.name)},applyStyle:function(t,e){e&&t.attr("style",e)},applyClass:function(t,e,n){e&&t.attr("class",e).attr("class",n+" "+t.attr("class"))},applyTransition:function(t,e){var n=e.graph();if(r.isPlainObject(n)){var i=n.transition;if(r.isFunction(i))return i(t)}return t}};var i=/:/g;function a(t){return t?String(t).replace(i,"\\:"):""}},5689:t=>{t.exports="0.6.4"},7188:(t,e,n)=>{n.r(e),n.d(e,{FormatSpecifier:()=>Xs,active:()=>qr,arc:()=>X_,area:()=>ex,areaRadial:()=>lx,ascending:()=>i,autoType:()=>ko,axisBottom:()=>it,axisLeft:()=>at,axisRight:()=>rt,axisTop:()=>nt,bisect:()=>u,bisectLeft:()=>c,bisectRight:()=>s,bisector:()=>a,blob:()=>rs,brush:()=>gi,brushSelection:()=>di,brushX:()=>pi,brushY:()=>yi,buffer:()=>as,chord:()=>Ti,clientPoint:()=>Mn,cluster:()=>dd,color:()=>$e,contourDensity:()=>Wa,contours:()=>Ya,create:()=>T_,creator:()=>re,cross:()=>f,csv:()=>ls,csvFormat:()=>uo,csvFormatBody:()=>lo,csvFormatRow:()=>fo,csvFormatRows:()=>ho,csvFormatValue:()=>po,csvParse:()=>so,csvParseRows:()=>co,cubehelix:()=>Na,curveBasis:()=>Hx,curveBasisClosed:()=>Wx,curveBasisOpen:()=>Gx,curveBundle:()=>Zx,curveCardinal:()=>Jx,curveCardinalClosed:()=>ew,curveCardinalOpen:()=>rw,curveCatmullRom:()=>ow,curveCatmullRomClosed:()=>cw,curveCatmullRomOpen:()=>lw,curveLinear:()=>K_,curveLinearClosed:()=>fw,curveMonotoneX:()=>_w,curveMonotoneY:()=>xw,curveNatural:()=>Tw,curveStep:()=>Cw,curveStepAfter:()=>Aw,curveStepBefore:()=>Sw,customEvent:()=>pe,descending:()=>d,deviation:()=>g,dispatch:()=>ht,drag:()=>Ja,dragDisable:()=>Ee,dragEnable:()=>Ce,dsv:()=>us,dsvFormat:()=>ao,easeBack:()=>Ko,easeBackIn:()=>Xo,easeBackInOut:()=>Ko,easeBackOut:()=>Zo,easeBounce:()=>Wo,easeBounceIn:()=>$o,easeBounceInOut:()=>Vo,easeBounceOut:()=>Wo,easeCircle:()=>qo,easeCircleIn:()=>zo,easeCircleInOut:()=>qo,easeCircleOut:()=>Uo,easeCubic:()=>jr,easeCubicIn:()=>Fr,easeCubicInOut:()=>jr,easeCubicOut:()=>Pr,easeElastic:()=>ts,easeElasticIn:()=>Jo,easeElasticInOut:()=>es,easeElasticOut:()=>ts,easeExp:()=>Yo,easeExpIn:()=>Po,easeExpInOut:()=>Yo,easeExpOut:()=>jo,easeLinear:()=>Eo,easePoly:()=>Do,easePolyIn:()=>Mo,easePolyInOut:()=>Do,easePolyOut:()=>No,easeQuad:()=>Ao,easeQuadIn:()=>Co,easeQuadInOut:()=>Ao,easeQuadOut:()=>So,easeSin:()=>Ro,easeSinIn:()=>Lo,easeSinInOut:()=>Ro,easeSinOut:()=>Io,entries:()=>ea,event:()=>ue,extent:()=>m,forceCenter:()=>bs,forceCollide:()=>Os,forceLink:()=>Is,forceManyBody:()=>Ys,forceRadial:()=>zs,forceSimulation:()=>js,forceX:()=>Us,forceY:()=>qs,format:()=>tc,formatDefaultLocale:()=>ac,formatLocale:()=>ic,formatPrefix:()=>ec,formatSpecifier:()=>Gs,geoAlbers:()=>Af,geoAlbersUsa:()=>Mf,geoArea:()=>eu,geoAzimuthalEqualArea:()=>Bf,geoAzimuthalEqualAreaRaw:()=>Of,geoAzimuthalEquidistant:()=>If,geoAzimuthalEquidistantRaw:()=>Lf,geoBounds:()=>Vu,geoCentroid:()=>al,geoCircle:()=>gl,geoClipAntimeridian:()=>Al,geoClipCircle:()=>Ml,geoClipExtent:()=>Bl,geoClipRectangle:()=>Ol,geoConicConformal:()=>zf,geoConicConformalRaw:()=>Yf,geoConicEqualArea:()=>Sf,geoConicEqualAreaRaw:()=>Cf,geoConicEquidistant:()=>$f,geoConicEquidistantRaw:()=>Hf,geoContains:()=>th,geoDistance:()=>$l,geoEqualEarth:()=>Qf,geoEqualEarthRaw:()=>Kf,geoEquirectangular:()=>qf,geoEquirectangularRaw:()=>Uf,geoGnomonic:()=>td,geoGnomonicRaw:()=>Jf,geoGraticule:()=>rh,geoGraticule10:()=>ih,geoIdentity:()=>ed,geoInterpolate:()=>ah,geoLength:()=>Ul,geoMercator:()=>Ff,geoMercatorRaw:()=>Rf,geoNaturalEarth1:()=>rd,geoNaturalEarth1Raw:()=>nd,geoOrthographic:()=>ad,geoOrthographicRaw:()=>id,geoPath:()=>uf,geoProjection:()=>kf,geoProjectionMutator:()=>Tf,geoRotation:()=>dl,geoStereographic:()=>sd,geoStereographicRaw:()=>od,geoStream:()=>Uc,geoTransform:()=>lf,geoTransverseMercator:()=>ud,geoTransverseMercatorRaw:()=>cd,gray:()=>la,hcl:()=>ba,hierarchy:()=>yd,histogram:()=>D,hsl:()=>nn,html:()=>ms,image:()=>fs,interpolate:()=>Sn,interpolateArray:()=>bn,interpolateBasis:()=>sn,interpolateBasisClosed:()=>cn,interpolateBlues:()=>Kb,interpolateBrBG:()=>ub,interpolateBuGn:()=>Sb,interpolateBuPu:()=>Mb,interpolateCividis:()=>c_,interpolateCool:()=>h_,interpolateCubehelix:()=>Cp,interpolateCubehelixDefault:()=>u_,interpolateCubehelixLong:()=>Sp,interpolateDate:()=>xn,interpolateDiscrete:()=>fp,interpolateGnBu:()=>Db,interpolateGreens:()=>Jb,interpolateGreys:()=>e_,interpolateHcl:()=>kp,interpolateHclLong:()=>Tp,interpolateHsl:()=>bp,interpolateHslLong:()=>_p,interpolateHue:()=>dp,interpolateInferno:()=>w_,interpolateLab:()=>xp,interpolateMagma:()=>x_,interpolateNumber:()=>wn,interpolateNumberArray:()=>mn,interpolateObject:()=>kn,interpolateOrRd:()=>Bb,interpolateOranges:()=>s_,interpolatePRGn:()=>hb,interpolatePiYG:()=>db,interpolatePlasma:()=>k_,interpolatePuBu:()=>Fb,interpolatePuBuGn:()=>Ib,interpolatePuOr:()=>yb,interpolatePuRd:()=>jb,interpolatePurples:()=>r_,interpolateRainbow:()=>d_,interpolateRdBu:()=>mb,interpolateRdGy:()=>bb,interpolateRdPu:()=>zb,interpolateRdYlBu:()=>xb,interpolateRdYlGn:()=>kb,interpolateReds:()=>a_,interpolateRgb:()=>dn,interpolateRgbBasis:()=>yn,interpolateRgbBasisClosed:()=>gn,interpolateRound:()=>pp,interpolateSinebow:()=>m_,interpolateSpectral:()=>Eb,interpolateString:()=>Cn,interpolateTransformCss:()=>fr,interpolateTransformSvg:()=>dr,interpolateTurbo:()=>v_,interpolateViridis:()=>__,interpolateWarm:()=>l_,interpolateYlGn:()=>$b,interpolateYlGnBu:()=>qb,interpolateYlOrBr:()=>Vb,interpolateYlOrRd:()=>Xb,interpolateZoom:()=>mp,interrupt:()=>rr,interval:()=>Xw,isoFormat:()=>Ww,isoParse:()=>Gw,json:()=>ps,keys:()=>Ji,lab:()=>ha,lch:()=>va,line:()=>tx,lineRadial:()=>ux,linkHorizontal:()=>bx,linkRadial:()=>xx,linkVertical:()=>_x,local:()=>C_,map:()=>qi,matcher:()=>gt,max:()=>I,mean:()=>R,median:()=>F,merge:()=>P,min:()=>j,mouse:()=>Dn,namespace:()=>Tt,namespaces:()=>kt,nest:()=>Hi,now:()=>Un,pack:()=>jd,packEnclose:()=>xd,packSiblings:()=>Ld,pairs:()=>l,partition:()=>$d,path:()=>Bi,permute:()=>Y,pie:()=>ix,piecewise:()=>Ap,pointRadial:()=>hx,polygonArea:()=>Np,polygonCentroid:()=>Dp,polygonContains:()=>Rp,polygonHull:()=>Ip,polygonLength:()=>Fp,precisionFixed:()=>oc,precisionPrefix:()=>sc,precisionRound:()=>cc,quadtree:()=>Cs,quantile:()=>O,quantize:()=>Mp,radialArea:()=>lx,radialLine:()=>ux,randomBates:()=>qp,randomExponential:()=>Hp,randomIrwinHall:()=>Up,randomLogNormal:()=>zp,randomNormal:()=>Yp,randomUniform:()=>jp,range:()=>k,rgb:()=>Xe,ribbon:()=>ji,scaleBand:()=>Qp,scaleDiverging:()=>$v,scaleDivergingLog:()=>Wv,scaleDivergingPow:()=>Gv,scaleDivergingSqrt:()=>Xv,scaleDivergingSymlog:()=>Vv,scaleIdentity:()=>py,scaleImplicit:()=>Zp,scaleLinear:()=>dy,scaleLog:()=>ky,scaleOrdinal:()=>Kp,scalePoint:()=>ty,scalePow:()=>Oy,scaleQuantile:()=>Ly,scaleQuantize:()=>Iy,scaleSequential:()=>Pv,scaleSequentialLog:()=>jv,scaleSequentialPow:()=>zv,scaleSequentialQuantile:()=>qv,scaleSequentialSqrt:()=>Uv,scaleSequentialSymlog:()=>Yv,scaleSqrt:()=>By,scaleSymlog:()=>Sy,scaleThreshold:()=>Ry,scaleTime:()=>Ev,scaleUtc:()=>Iv,scan:()=>z,schemeAccent:()=>Qv,schemeBlues:()=>Zb,schemeBrBG:()=>cb,schemeBuGn:()=>Cb,schemeBuPu:()=>Ab,schemeCategory10:()=>Kv,schemeDark2:()=>Jv,schemeGnBu:()=>Nb,schemeGreens:()=>Qb,schemeGreys:()=>t_,schemeOrRd:()=>Ob,schemeOranges:()=>o_,schemePRGn:()=>lb,schemePaired:()=>tb,schemePastel1:()=>eb,schemePastel2:()=>nb,schemePiYG:()=>fb,schemePuBu:()=>Rb,schemePuBuGn:()=>Lb,schemePuOr:()=>pb,schemePuRd:()=>Pb,schemePurples:()=>n_,schemeRdBu:()=>gb,schemeRdGy:()=>vb,schemeRdPu:()=>Yb,schemeRdYlBu:()=>_b,schemeRdYlGn:()=>wb,schemeReds:()=>i_,schemeSet1:()=>rb,schemeSet2:()=>ib,schemeSet3:()=>ab,schemeSpectral:()=>Tb,schemeTableau10:()=>ob,schemeYlGn:()=>Hb,schemeYlGnBu:()=>Ub,schemeYlOrBr:()=>Wb,schemeYlOrRd:()=>Gb,select:()=>we,selectAll:()=>A_,selection:()=>xe,selector:()=>dt,selectorAll:()=>yt,set:()=>Qi,shuffle:()=>U,stack:()=>Ow,stackOffsetDiverging:()=>Lw,stackOffsetExpand:()=>Bw,stackOffsetNone:()=>Mw,stackOffsetSilhouette:()=>Iw,stackOffsetWiggle:()=>Rw,stackOrderAppearance:()=>Fw,stackOrderAscending:()=>jw,stackOrderDescending:()=>zw,stackOrderInsideOut:()=>Uw,stackOrderNone:()=>Nw,stackOrderReverse:()=>qw,stratify:()=>Zd,style:()=>It,sum:()=>q,svg:()=>vs,symbol:()=>Yx,symbolCircle:()=>wx,symbolCross:()=>kx,symbolDiamond:()=>Cx,symbolSquare:()=>Dx,symbolStar:()=>Nx,symbolTriangle:()=>Bx,symbolWye:()=>Px,symbols:()=>jx,text:()=>ss,thresholdFreedmanDiaconis:()=>B,thresholdScott:()=>L,thresholdSturges:()=>N,tickFormat:()=>hy,tickIncrement:()=>A,tickStep:()=>M,ticks:()=>S,timeDay:()=>dg,timeDays:()=>pg,timeFormat:()=>Jg,timeFormatDefaultLocale:()=>_v,timeFormatLocale:()=>Kg,timeFriday:()=>rg,timeFridays:()=>lg,timeHour:()=>gg,timeHours:()=>mg,timeInterval:()=>jy,timeMillisecond:()=>Eg,timeMilliseconds:()=>Cg,timeMinute:()=>bg,timeMinutes:()=>_g,timeMonday:()=>Jy,timeMondays:()=>og,timeMonth:()=>Hy,timeMonths:()=>$y,timeParse:()=>tm,timeSaturday:()=>ig,timeSaturdays:()=>hg,timeSecond:()=>wg,timeSeconds:()=>kg,timeSunday:()=>Qy,timeSundays:()=>ag,timeThursday:()=>ng,timeThursdays:()=>ug,timeTuesday:()=>tg,timeTuesdays:()=>sg,timeWednesday:()=>eg,timeWednesdays:()=>cg,timeWeek:()=>Qy,timeWeeks:()=>ag,timeYear:()=>zy,timeYears:()=>Uy,timeout:()=>Zn,timer:()=>$n,timerFlush:()=>Wn,touch:()=>Nn,touches:()=>M_,transition:()=>Lr,transpose:()=>H,tree:()=>rp,treemap:()=>cp,treemapBinary:()=>up,treemapDice:()=>Hd,treemapResquarify:()=>hp,treemapSlice:()=>ip,treemapSliceDice:()=>lp,treemapSquarify:()=>sp,tsv:()=>hs,tsvFormat:()=>vo,tsvFormatBody:()=>bo,tsvFormatRow:()=>xo,tsvFormatRows:()=>_o,tsvFormatValue:()=>wo,tsvParse:()=>go,tsvParseRows:()=>mo,utcDay:()=>qg,utcDays:()=>Hg,utcFormat:()=>em,utcFriday:()=>Bg,utcFridays:()=>Yg,utcHour:()=>Nv,utcHours:()=>Dv,utcMillisecond:()=>Eg,utcMilliseconds:()=>Cg,utcMinute:()=>Bv,utcMinutes:()=>Lv,utcMonday:()=>Mg,utcMondays:()=>Rg,utcMonth:()=>Sv,utcMonths:()=>Av,utcParse:()=>nm,utcSaturday:()=>Lg,utcSaturdays:()=>zg,utcSecond:()=>wg,utcSeconds:()=>kg,utcSunday:()=>Ag,utcSundays:()=>Ig,utcThursday:()=>Og,utcThursdays:()=>jg,utcTuesday:()=>Ng,utcTuesdays:()=>Fg,utcWednesday:()=>Dg,utcWednesdays:()=>Pg,utcWeek:()=>Ag,utcWeeks:()=>Ig,utcYear:()=>Wg,utcYears:()=>Vg,values:()=>ta,variance:()=>y,version:()=>r,voronoi:()=>Ik,window:()=>Dt,xml:()=>gs,zip:()=>W,zoom:()=>Xk,zoomIdentity:()=>jk,zoomTransform:()=>Yk});var r="5.16.0";function i(t,e){return te?1:t>=e?0:NaN}function a(t){var e;return 1===t.length&&(e=t,t=function(t,n){return i(e(t),n)}),{left:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r>>1;t(e[a],n)<0?r=a+1:i=a}return r},right:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r>>1;t(e[a],n)>0?i=a:r=a+1}return r}}}var o=a(i),s=o.right,c=o.left;const u=s;function l(t,e){null==e&&(e=h);for(var n=0,r=t.length-1,i=t[0],a=new Array(r<0?0:r);nt?1:e>=t?0:NaN}function p(t){return null===t?NaN:+t}function y(t,e){var n,r,i=t.length,a=0,o=-1,s=0,c=0;if(null==e)for(;++o1)return c/(a-1)}function g(t,e){var n=y(t,e);return n?Math.sqrt(n):n}function m(t,e){var n,r,i,a=t.length,o=-1;if(null==e){for(;++o=n)for(r=i=n;++on&&(r=n),i=n)for(r=i=n;++on&&(r=n),i0)return[t];if((r=e0)for(t=Math.ceil(t/o),e=Math.floor(e/o),a=new Array(i=Math.ceil(e-t+1));++s=0?(a>=T?10:a>=E?5:a>=C?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(a>=T?10:a>=E?5:a>=C?2:1)}function M(t,e,n){var r=Math.abs(e-t)/Math.max(0,n),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),a=r/i;return a>=T?i*=10:a>=E?i*=5:a>=C&&(i*=2),eh;)f.pop(),--d;var p,y=new Array(d+1);for(i=0;i<=d;++i)(p=y[i]=[]).x0=i>0?f[i-1]:l,p.x1=i=1)return+n(t[r-1],r-1,t);var r,i=(r-1)*e,a=Math.floor(i),o=+n(t[a],a,t);return o+(+n(t[a+1],a+1,t)-o)*(i-a)}}function B(t,e,n){return t=_.call(t,p).sort(i),Math.ceil((n-e)/(2*(O(t,.75)-O(t,.25))*Math.pow(t.length,-1/3)))}function L(t,e,n){return Math.ceil((n-e)/(3.5*g(t)*Math.pow(t.length,-1/3)))}function I(t,e){var n,r,i=t.length,a=-1;if(null==e){for(;++a=n)for(r=n;++ar&&(r=n)}else for(;++a=n)for(r=n;++ar&&(r=n);return r}function R(t,e){var n,r=t.length,i=r,a=-1,o=0;if(null==e)for(;++a=0;)for(e=(r=t[i]).length;--e>=0;)n[--o]=r[e];return n}function j(t,e){var n,r,i=t.length,a=-1;if(null==e){for(;++a=n)for(r=n;++an&&(r=n)}else for(;++a=n)for(r=n;++an&&(r=n);return r}function Y(t,e){for(var n=e.length,r=new Array(n);n--;)r[n]=t[e[n]];return r}function z(t,e){if(n=t.length){var n,r,a=0,o=0,s=t[o];for(null==e&&(e=i);++a=0&&(n=t.slice(r+1),t=t.slice(0,r)),t&&!e.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))}(t+"",r),a=-1,o=i.length;if(!(arguments.length<2)){if(null!=e&&"function"!=typeof e)throw new Error("invalid callback: "+e);for(;++a0)for(var n,r,i=new Array(n),a=0;ae?1:t>=e?0:NaN}vt.prototype={constructor:vt,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,e){return this._parent.insertBefore(t,e)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};var wt="http://www.w3.org/1999/xhtml";const kt={svg:"http://www.w3.org/2000/svg",xhtml:wt,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};function Tt(t){var e=t+="",n=e.indexOf(":");return n>=0&&"xmlns"!==(e=t.slice(0,n))&&(t=t.slice(n+1)),kt.hasOwnProperty(e)?{space:kt[e],local:t}:t}function Et(t){return function(){this.removeAttribute(t)}}function Ct(t){return function(){this.removeAttributeNS(t.space,t.local)}}function St(t,e){return function(){this.setAttribute(t,e)}}function At(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function Mt(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttribute(t):this.setAttribute(t,n)}}function Nt(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,n)}}function Dt(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function Ot(t){return function(){this.style.removeProperty(t)}}function Bt(t,e,n){return function(){this.style.setProperty(t,e,n)}}function Lt(t,e,n){return function(){var r=e.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,n)}}function It(t,e){return t.style.getPropertyValue(e)||Dt(t).getComputedStyle(t,null).getPropertyValue(e)}function Rt(t){return function(){delete this[t]}}function Ft(t,e){return function(){this[t]=e}}function Pt(t,e){return function(){var n=e.apply(this,arguments);null==n?delete this[t]:this[t]=n}}function jt(t){return t.trim().split(/^|\s+/)}function Yt(t){return t.classList||new zt(t)}function zt(t){this._node=t,this._names=jt(t.getAttribute("class")||"")}function Ut(t,e){for(var n=Yt(t),r=-1,i=e.length;++r=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var ce={},ue=null;function le(t,e,n){return t=he(t,e,n),function(e){var n=e.relatedTarget;n&&(n===this||8&n.compareDocumentPosition(this))||t.call(this,e)}}function he(t,e,n){return function(r){var i=ue;ue=r;try{t.call(this,this.__data__,e,n)}finally{ue=i}}}function fe(t){return function(){var e=this.__on;if(e){for(var n,r=0,i=-1,a=e.length;r=x&&(x=_+1);!(b=m[x])&&++x=0;)(r=i[a])&&(o&&4^r.compareDocumentPosition(o)&&o.parentNode.insertBefore(r,o),o=r);return this},sort:function(t){function e(e,n){return e&&n?t(e.__data__,n.__data__):!e-!n}t||(t=xt);for(var n=this._groups,r=n.length,i=new Array(r),a=0;a1?this.each((null==e?Ot:"function"==typeof e?Lt:Bt)(t,e,null==n?"":n)):It(this.node(),t)},property:function(t,e){return arguments.length>1?this.each((null==e?Rt:"function"==typeof e?Pt:Ft)(t,e)):this.node()[t]},classed:function(t,e){var n=jt(t+"");if(arguments.length<2){for(var r=Yt(this.node()),i=-1,a=n.length;++i=0&&(e=t.slice(n+1),t=t.slice(0,n)),{type:t,name:e}}))}(t+""),o=a.length;if(!(arguments.length<2)){for(s=e?de:fe,null==n&&(n=!1),r=0;r>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===n?Ve(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===n?Ve(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=Re.exec(t))?new Ze(e[1],e[2],e[3],1):(e=Fe.exec(t))?new Ze(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=Pe.exec(t))?Ve(e[1],e[2],e[3],e[4]):(e=je.exec(t))?Ve(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=Ye.exec(t))?tn(e[1],e[2]/100,e[3]/100,1):(e=ze.exec(t))?tn(e[1],e[2]/100,e[3]/100,e[4]):Ue.hasOwnProperty(t)?We(Ue[t]):"transparent"===t?new Ze(NaN,NaN,NaN,0):null}function We(t){return new Ze(t>>16&255,t>>8&255,255&t,1)}function Ve(t,e,n,r){return r<=0&&(t=e=n=NaN),new Ze(t,e,n,r)}function Ge(t){return t instanceof Me||(t=$e(t)),t?new Ze((t=t.rgb()).r,t.g,t.b,t.opacity):new Ze}function Xe(t,e,n,r){return 1===arguments.length?Ge(t):new Ze(t,e,n,null==r?1:r)}function Ze(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}function Ke(){return"#"+Je(this.r)+Je(this.g)+Je(this.b)}function Qe(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function Je(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function tn(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new rn(t,e,n,r)}function en(t){if(t instanceof rn)return new rn(t.h,t.s,t.l,t.opacity);if(t instanceof Me||(t=$e(t)),!t)return new rn;if(t instanceof rn)return t;var e=(t=t.rgb()).r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),a=Math.max(e,n,r),o=NaN,s=a-i,c=(a+i)/2;return s?(o=e===a?(n-r)/s+6*(n0&&c<1?0:o,new rn(o,s,c,t.opacity)}function nn(t,e,n,r){return 1===arguments.length?en(t):new rn(t,e,n,null==r?1:r)}function rn(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}function an(t,e,n){return 255*(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)}function on(t,e,n,r,i){var a=t*t,o=a*t;return((1-3*t+3*a-o)*e+(4-6*a+3*o)*n+(1+3*t+3*a-3*o)*r+o*i)/6}function sn(t){var e=t.length-1;return function(n){var r=n<=0?n=0:n>=1?(n=1,e-1):Math.floor(n*e),i=t[r],a=t[r+1],o=r>0?t[r-1]:2*i-a,s=r180||n<-180?n-360*Math.round(n/360):n):un(isNaN(t)?e:t)}function fn(t,e){var n=e-t;return n?ln(t,n):un(isNaN(t)?e:t)}Se(Me,$e,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:qe,formatHex:qe,formatHsl:function(){return en(this).formatHsl()},formatRgb:He,toString:He}),Se(Ze,Xe,Ae(Me,{brighter:function(t){return t=null==t?De:Math.pow(De,t),new Ze(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?Ne:Math.pow(Ne,t),new Ze(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:Ke,formatHex:Ke,formatRgb:Qe,toString:Qe})),Se(rn,nn,Ae(Me,{brighter:function(t){return t=null==t?De:Math.pow(De,t),new rn(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?Ne:Math.pow(Ne,t),new rn(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new Ze(an(t>=240?t-240:t+120,i,r),an(t,i,r),an(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));const dn=function t(e){var n=function(t){return 1==(t=+t)?fn:function(e,n){return n-e?function(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(r){return Math.pow(t+r*e,n)}}(e,n,t):un(isNaN(e)?n:e)}}(e);function r(t,e){var r=n((t=Xe(t)).r,(e=Xe(e)).r),i=n(t.g,e.g),a=n(t.b,e.b),o=fn(t.opacity,e.opacity);return function(e){return t.r=r(e),t.g=i(e),t.b=a(e),t.opacity=o(e),t+""}}return r.gamma=t,r}(1);function pn(t){return function(e){var n,r,i=e.length,a=new Array(i),o=new Array(i),s=new Array(i);for(n=0;na&&(i=e.slice(a,i),s[o]?s[o]+=i:s[++o]=i),(n=n[0])===(r=r[0])?s[o]?s[o]+=r:s[++o]=r:(s[++o]=null,c.push({i:o,x:wn(n,r)})),a=En.lastIndex;return a=0&&e._call.call(null,t),e=e._next;--Ln}function Vn(){Pn=(Fn=Yn.now())+jn,Ln=In=0;try{Wn()}finally{Ln=0,function(){for(var t,e,n=On,r=1/0;n;)n._call?(r>n._time&&(r=n._time),t=n,n=n._next):(e=n._next,n._next=null,n=t?t._next=e:On=e);Bn=t,Xn(r)}(),Pn=0}}function Gn(){var t=Yn.now(),e=t-Fn;e>1e3&&(jn-=e,Fn=t)}function Xn(t){Ln||(In&&(In=clearTimeout(In)),t-Pn>24?(t<1/0&&(In=setTimeout(Vn,t-Yn.now()-jn)),Rn&&(Rn=clearInterval(Rn))):(Rn||(Fn=Yn.now(),Rn=setInterval(Gn,1e3)),Ln=1,zn(Vn)))}function Zn(t,e,n){var r=new Hn;return e=null==e?0:+e,r.restart((function(n){r.stop(),t(n+e)}),e,n),r}Hn.prototype=$n.prototype={constructor:Hn,restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?Un():+n)+(null==e?0:+e),this._next||Bn===this||(Bn?Bn._next=this:On=this,Bn=this),this._call=t,this._time=n,Xn()},stop:function(){this._call&&(this._call=null,this._time=1/0,Xn())}};var Kn=ht("start","end","cancel","interrupt"),Qn=[];function Jn(t,e,n,r,i,a){var o=t.__transition;if(o){if(n in o)return}else t.__transition={};!function(t,e,n){var r,i=t.__transition;function a(c){var u,l,h,f;if(1!==n.state)return s();for(u in i)if((f=i[u]).name===n.name){if(3===f.state)return Zn(a);4===f.state?(f.state=6,f.timer.stop(),f.on.call("interrupt",t,t.__data__,f.index,f.group),delete i[u]):+u0)throw new Error("too late; already scheduled");return n}function er(t,e){var n=nr(t,e);if(n.state>3)throw new Error("too late; already running");return n}function nr(t,e){var n=t.__transition;if(!n||!(n=n[e]))throw new Error("transition not found");return n}function rr(t,e){var n,r,i,a=t.__transition,o=!0;if(a){for(i in e=null==e?null:e+"",a)(n=a[i]).name===e?(r=n.state>2&&n.state<5,n.state=6,n.timer.stop(),n.on.call(r?"interrupt":"cancel",t,t.__data__,n.index,n.group),delete a[i]):o=!1;o&&delete t.__transition}}var ir,ar,or,sr,cr=180/Math.PI,ur={translateX:0,translateY:0,rotate:0,skewX:0,scaleX:1,scaleY:1};function lr(t,e,n,r,i,a){var o,s,c;return(o=Math.sqrt(t*t+e*e))&&(t/=o,e/=o),(c=t*n+e*r)&&(n-=t*c,r-=e*c),(s=Math.sqrt(n*n+r*r))&&(n/=s,r/=s,c/=s),t*r180?e+=360:e-t>180&&(t+=360),a.push({i:n.push(i(n)+"rotate(",null,r)-2,x:wn(t,e)})):e&&n.push(i(n)+"rotate("+e+r)}(a.rotate,o.rotate,s,c),function(t,e,n,a){t!==e?a.push({i:n.push(i(n)+"skewX(",null,r)-2,x:wn(t,e)}):e&&n.push(i(n)+"skewX("+e+r)}(a.skewX,o.skewX,s,c),function(t,e,n,r,a,o){if(t!==n||e!==r){var s=a.push(i(a)+"scale(",null,",",null,")");o.push({i:s-4,x:wn(t,n)},{i:s-2,x:wn(e,r)})}else 1===n&&1===r||a.push(i(a)+"scale("+n+","+r+")")}(a.scaleX,a.scaleY,o.scaleX,o.scaleY,s,c),a=o=null,function(t){for(var e,n=-1,r=c.length;++n=0&&(t=t.slice(0,e)),!t||"start"===t}))}(e)?tr:er;return function(){var o=a(this,t),s=o.on;s!==r&&(i=(r=s).copy()).on(e,n),o.on=i}}(n,t,e))},attr:function(t,e){var n=Tt(t),r="transform"===n?dr:mr;return this.attrTween(t,"function"==typeof e?(n.local?kr:wr)(n,r,gr(this,"attr."+t,e)):null==e?(n.local?br:vr)(n):(n.local?xr:_r)(n,r,e))},attrTween:function(t,e){var n="attr."+t;if(arguments.length<2)return(n=this.tween(n))&&n._value;if(null==e)return this.tween(n,null);if("function"!=typeof e)throw new Error;var r=Tt(t);return this.tween(n,(r.local?Tr:Er)(r,e))},style:function(t,e,n){var r="transform"==(t+="")?fr:mr;return null==e?this.styleTween(t,function(t,e){var n,r,i;return function(){var a=It(this,t),o=(this.style.removeProperty(t),It(this,t));return a===o?null:a===n&&o===r?i:i=e(n=a,r=o)}}(t,r)).on("end.style."+t,Dr(t)):"function"==typeof e?this.styleTween(t,function(t,e,n){var r,i,a;return function(){var o=It(this,t),s=n(this),c=s+"";return null==s&&(this.style.removeProperty(t),c=s=It(this,t)),o===c?null:o===r&&c===i?a:(i=c,a=e(r=o,s))}}(t,r,gr(this,"style."+t,e))).each(function(t,e){var n,r,i,a,o="style."+e,s="end."+o;return function(){var c=er(this,t),u=c.on,l=null==c.value[o]?a||(a=Dr(e)):void 0;u===n&&i===l||(r=(n=u).copy()).on(s,i=l),c.on=r}}(this._id,t)):this.styleTween(t,function(t,e,n){var r,i,a=n+"";return function(){var o=It(this,t);return o===a?null:o===r?i:i=e(r=o,n)}}(t,r,e),n).on("end.style."+t,null)},styleTween:function(t,e,n){var r="style."+(t+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(null==e)return this.tween(r,null);if("function"!=typeof e)throw new Error;return this.tween(r,function(t,e,n){var r,i;function a(){var a=e.apply(this,arguments);return a!==i&&(r=(i=a)&&function(t,e,n){return function(r){this.style.setProperty(t,e.call(this,r),n)}}(t,a,n)),r}return a._value=e,a}(t,e,null==n?"":n))},text:function(t){return this.tween("text","function"==typeof t?function(t){return function(){var e=t(this);this.textContent=null==e?"":e}}(gr(this,"text",t)):function(t){return function(){this.textContent=t}}(null==t?"":t+""))},textTween:function(t){var e="text";if(arguments.length<1)return(e=this.tween(e))&&e._value;if(null==t)return this.tween(e,null);if("function"!=typeof t)throw new Error;return this.tween(e,function(t){var e,n;function r(){var r=t.apply(this,arguments);return r!==n&&(e=(n=r)&&function(t){return function(e){this.textContent=t.call(this,e)}}(r)),e}return r._value=t,r}(t))},remove:function(){return this.on("end.remove",function(t){return function(){var e=this.parentNode;for(var n in this.__transition)if(+n!==t)return;e&&e.removeChild(this)}}(this._id))},tween:function(t,e){var n=this._id;if(t+="",arguments.length<2){for(var r,i=nr(this.node(),n).tween,a=0,o=i.length;a1&&n.name===e)return new Br([[t]],Ur,e,+r);return null}function Hr(t){return function(){return t}}function $r(t,e,n){this.target=t,this.type=e,this.selection=n}function Wr(){ue.stopImmediatePropagation()}function Vr(){ue.preventDefault(),ue.stopImmediatePropagation()}var Gr={name:"drag"},Xr={name:"space"},Zr={name:"handle"},Kr={name:"center"};function Qr(t){return[+t[0],+t[1]]}function Jr(t){return[Qr(t[0]),Qr(t[1])]}var ti={name:"x",handles:["w","e"].map(ci),input:function(t,e){return null==t?null:[[+t[0],e[0][1]],[+t[1],e[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},ei={name:"y",handles:["n","s"].map(ci),input:function(t,e){return null==t?null:[[e[0][0],+t[0]],[e[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},ni={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(ci),input:function(t){return null==t?null:Jr(t)},output:function(t){return t}},ri={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},ii={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},ai={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},oi={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},si={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function ci(t){return{type:t}}function ui(){return!ue.ctrlKey&&!ue.button}function li(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function hi(){return navigator.maxTouchPoints||"ontouchstart"in this}function fi(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function di(t){var e=t.__brush;return e?e.dim.output(e.selection):null}function pi(){return mi(ti)}function yi(){return mi(ei)}function gi(){return mi(ni)}function mi(t){var e,n=li,r=ui,i=hi,a=!0,o=ht("start","brush","end"),s=6;function c(e){var n=e.property("__brush",y).selectAll(".overlay").data([ci("overlay")]);n.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",ri.overlay).merge(n).each((function(){var t=fi(this).extent;we(this).attr("x",t[0][0]).attr("y",t[0][1]).attr("width",t[1][0]-t[0][0]).attr("height",t[1][1]-t[0][1])})),e.selectAll(".selection").data([ci("selection")]).enter().append("rect").attr("class","selection").attr("cursor",ri.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var r=e.selectAll(".handle").data(t.handles,(function(t){return t.type}));r.exit().remove(),r.enter().append("rect").attr("class",(function(t){return"handle handle--"+t.type})).attr("cursor",(function(t){return ri[t.type]})),e.each(u).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",f).filter(i).on("touchstart.brush",f).on("touchmove.brush",d).on("touchend.brush touchcancel.brush",p).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function u(){var t=we(this),e=fi(this).selection;e?(t.selectAll(".selection").style("display",null).attr("x",e[0][0]).attr("y",e[0][1]).attr("width",e[1][0]-e[0][0]).attr("height",e[1][1]-e[0][1]),t.selectAll(".handle").style("display",null).attr("x",(function(t){return"e"===t.type[t.type.length-1]?e[1][0]-s/2:e[0][0]-s/2})).attr("y",(function(t){return"s"===t.type[0]?e[1][1]-s/2:e[0][1]-s/2})).attr("width",(function(t){return"n"===t.type||"s"===t.type?e[1][0]-e[0][0]+s:s})).attr("height",(function(t){return"e"===t.type||"w"===t.type?e[1][1]-e[0][1]+s:s}))):t.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function l(t,e,n){var r=t.__brush.emitter;return!r||n&&r.clean?new h(t,e,n):r}function h(t,e,n){this.that=t,this.args=e,this.state=t.__brush,this.active=0,this.clean=n}function f(){if((!e||ue.touches)&&r.apply(this,arguments)){var n,i,o,s,c,h,f,d,p,y,g,m=this,v=ue.target.__data__.type,b="selection"===(a&&ue.metaKey?v="overlay":v)?Gr:a&&ue.altKey?Kr:Zr,_=t===ei?null:oi[v],x=t===ti?null:si[v],w=fi(m),k=w.extent,T=w.selection,E=k[0][0],C=k[0][1],S=k[1][0],A=k[1][1],M=0,N=0,D=_&&x&&a&&ue.shiftKey,O=ue.touches?function(t){return function(e){return Nn(e,ue.touches,t)}}(ue.changedTouches[0].identifier):Dn,B=O(m),L=B,I=l(m,arguments,!0).beforestart();"overlay"===v?(T&&(p=!0),w.selection=T=[[n=t===ei?E:B[0],o=t===ti?C:B[1]],[c=t===ei?S:n,f=t===ti?A:o]]):(n=T[0][0],o=T[0][1],c=T[1][0],f=T[1][1]),i=n,s=o,h=c,d=f;var R=we(m).attr("pointer-events","none"),F=R.selectAll(".overlay").attr("cursor",ri[v]);if(ue.touches)I.moved=j,I.ended=z;else{var P=we(ue.view).on("mousemove.brush",j,!0).on("mouseup.brush",z,!0);a&&P.on("keydown.brush",(function(){switch(ue.keyCode){case 16:D=_&&x;break;case 18:b===Zr&&(_&&(c=h-M*_,n=i+M*_),x&&(f=d-N*x,o=s+N*x),b=Kr,Y());break;case 32:b!==Zr&&b!==Kr||(_<0?c=h-M:_>0&&(n=i-M),x<0?f=d-N:x>0&&(o=s-N),b=Xr,F.attr("cursor",ri.selection),Y());break;default:return}Vr()}),!0).on("keyup.brush",(function(){switch(ue.keyCode){case 16:D&&(y=g=D=!1,Y());break;case 18:b===Kr&&(_<0?c=h:_>0&&(n=i),x<0?f=d:x>0&&(o=s),b=Zr,Y());break;case 32:b===Xr&&(ue.altKey?(_&&(c=h-M*_,n=i+M*_),x&&(f=d-N*x,o=s+N*x),b=Kr):(_<0?c=h:_>0&&(n=i),x<0?f=d:x>0&&(o=s),b=Zr),F.attr("cursor",ri[v]),Y());break;default:return}Vr()}),!0),Ee(ue.view)}Wr(),rr(m),u.call(m),I.start()}function j(){var t=O(m);!D||y||g||(Math.abs(t[0]-L[0])>Math.abs(t[1]-L[1])?g=!0:y=!0),L=t,p=!0,Vr(),Y()}function Y(){var t;switch(M=L[0]-B[0],N=L[1]-B[1],b){case Xr:case Gr:_&&(M=Math.max(E-n,Math.min(S-c,M)),i=n+M,h=c+M),x&&(N=Math.max(C-o,Math.min(A-f,N)),s=o+N,d=f+N);break;case Zr:_<0?(M=Math.max(E-n,Math.min(S-n,M)),i=n+M,h=c):_>0&&(M=Math.max(E-c,Math.min(S-c,M)),i=n,h=c+M),x<0?(N=Math.max(C-o,Math.min(A-o,N)),s=o+N,d=f):x>0&&(N=Math.max(C-f,Math.min(A-f,N)),s=o,d=f+N);break;case Kr:_&&(i=Math.max(E,Math.min(S,n-M*_)),h=Math.max(E,Math.min(S,c+M*_))),x&&(s=Math.max(C,Math.min(A,o-N*x)),d=Math.max(C,Math.min(A,f+N*x)))}hMi)if(Math.abs(l*s-c*u)>Mi&&i){var f=n-a,d=r-o,p=s*s+c*c,y=f*f+d*d,g=Math.sqrt(p),m=Math.sqrt(h),v=i*Math.tan((Si-Math.acos((p+h-y)/(2*g*m)))/2),b=v/m,_=v/g;Math.abs(b-1)>Mi&&(this._+="L"+(t+b*u)+","+(e+b*l)),this._+="A"+i+","+i+",0,0,"+ +(l*f>u*d)+","+(this._x1=t+_*s)+","+(this._y1=e+_*c)}else this._+="L"+(this._x1=t)+","+(this._y1=e)},arc:function(t,e,n,r,i,a){t=+t,e=+e,a=!!a;var o=(n=+n)*Math.cos(r),s=n*Math.sin(r),c=t+o,u=e+s,l=1^a,h=a?r-i:i-r;if(n<0)throw new Error("negative radius: "+n);null===this._x1?this._+="M"+c+","+u:(Math.abs(this._x1-c)>Mi||Math.abs(this._y1-u)>Mi)&&(this._+="L"+c+","+u),n&&(h<0&&(h=h%Ai+Ai),h>Ni?this._+="A"+n+","+n+",0,1,"+l+","+(t-o)+","+(e-s)+"A"+n+","+n+",0,1,"+l+","+(this._x1=c)+","+(this._y1=u):h>Mi&&(this._+="A"+n+","+n+",0,"+ +(h>=Si)+","+l+","+(this._x1=t+n*Math.cos(i))+","+(this._y1=e+n*Math.sin(i))))},rect:function(t,e,n,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +n+"v"+ +r+"h"+-n+"Z"},toString:function(){return this._}};const Bi=Oi;function Li(t){return t.source}function Ii(t){return t.target}function Ri(t){return t.radius}function Fi(t){return t.startAngle}function Pi(t){return t.endAngle}function ji(){var t=Li,e=Ii,n=Ri,r=Fi,i=Pi,a=null;function o(){var o,s=Ei.call(arguments),c=t.apply(this,s),u=e.apply(this,s),l=+n.apply(this,(s[0]=c,s)),h=r.apply(this,s)-xi,f=i.apply(this,s)-xi,d=l*vi(h),p=l*bi(h),y=+n.apply(this,(s[0]=u,s)),g=r.apply(this,s)-xi,m=i.apply(this,s)-xi;if(a||(a=o=Bi()),a.moveTo(d,p),a.arc(0,0,l,h,f),h===g&&f===m||(a.quadraticCurveTo(0,0,y*vi(g),y*bi(g)),a.arc(0,0,y,g,m)),a.quadraticCurveTo(0,0,d,p),a.closePath(),o)return a=null,o+""||null}return o.radius=function(t){return arguments.length?(n="function"==typeof t?t:Ci(+t),o):n},o.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:Ci(+t),o):r},o.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:Ci(+t),o):i},o.source=function(e){return arguments.length?(t=e,o):t},o.target=function(t){return arguments.length?(e=t,o):e},o.context=function(t){return arguments.length?(a=null==t?null:t,o):a},o}var Yi="$";function zi(){}function Ui(t,e){var n=new zi;if(t instanceof zi)t.each((function(t,e){n.set(e,t)}));else if(Array.isArray(t)){var r,i=-1,a=t.length;if(null==e)for(;++i=r.length)return null!=t&&n.sort(t),null!=e?e(n):n;for(var c,u,l,h=-1,f=n.length,d=r[i++],p=qi(),y=o();++hr.length)return t;var a,s=i[n-1];return null!=e&&n>=r.length?a=t.entries():(a=[],t.each((function(t,e){a.push({key:e,values:o(t,n)})}))),null!=s?a.sort((function(t,e){return s(t.key,e.key)})):a}return n={object:function(t){return a(t,0,$i,Wi)},map:function(t){return a(t,0,Vi,Gi)},entries:function(t){return o(a(t,0,Vi,Gi),0)},key:function(t){return r.push(t),n},sortKeys:function(t){return i[r.length-1]=t,n},sortValues:function(e){return t=e,n},rollup:function(t){return e=t,n}}}function $i(){return{}}function Wi(t,e,n){t[e]=n}function Vi(){return qi()}function Gi(t,e,n){t.set(e,n)}function Xi(){}var Zi=qi.prototype;function Ki(t,e){var n=new Xi;if(t instanceof Xi)t.each((function(t){n.add(t)}));else if(t){var r=-1,i=t.length;if(null==e)for(;++r.008856451679035631?Math.pow(t,1/3):t/ca+oa}function pa(t){return t>sa?t*t*t:ca*(t-oa)}function ya(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function ga(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function ma(t){if(t instanceof _a)return new _a(t.h,t.c,t.l,t.opacity);if(t instanceof fa||(t=ua(t)),0===t.a&&0===t.b)return new _a(NaN,0r!=d>r&&n<(f-u)*(r-l)/(d-l)+u&&(i=-i)}return i}function Fa(t,e,n){var r,i,a,o;return function(t,e,n){return(e[0]-t[0])*(n[1]-t[1])==(n[0]-t[0])*(e[1]-t[1])}(t,e,n)&&(i=t[r=+(t[0]===e[0])],a=n[r],o=e[r],i<=a&&a<=o||o<=a&&a<=i)}function Pa(){}var ja=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]];function Ya(){var t=1,e=1,n=N,r=s;function i(t){var e=n(t);if(Array.isArray(e))e=e.slice().sort(Ba);else{var r=m(t),i=r[0],o=r[1];e=M(i,o,e),e=k(Math.floor(i/e)*e,Math.floor(o/e)*e,e)}return e.map((function(e){return a(t,e)}))}function a(n,i){var a=[],s=[];return function(n,r,i){var a,s,c,u,l,h,f=new Array,d=new Array;for(a=s=-1,u=n[0]>=r,ja[u<<1].forEach(p);++a=r,ja[c|u<<1].forEach(p);for(ja[u|0].forEach(p);++s=r,l=n[s*t]>=r,ja[u<<1|l<<2].forEach(p);++a=r,h=l,l=n[s*t+a+1]>=r,ja[c|u<<1|l<<2|h<<3].forEach(p);ja[u|l<<3].forEach(p)}for(a=-1,l=n[s*t]>=r,ja[l<<2].forEach(p);++a=r,ja[l<<2|h<<3].forEach(p);function p(t){var e,n,r=[t[0][0]+a,t[0][1]+s],c=[t[1][0]+a,t[1][1]+s],u=o(r),l=o(c);(e=d[u])?(n=f[l])?(delete d[e.end],delete f[n.start],e===n?(e.ring.push(c),i(e.ring)):f[e.start]=d[n.end]={start:e.start,end:n.end,ring:e.ring.concat(n.ring)}):(delete d[e.end],e.ring.push(c),d[e.end=l]=e):(e=f[l])?(n=d[u])?(delete f[e.start],delete d[n.end],e===n?(e.ring.push(c),i(e.ring)):f[n.start]=d[e.end]={start:n.start,end:e.end,ring:n.ring.concat(e.ring)}):(delete f[e.start],e.ring.unshift(r),f[e.start=u]=e):f[u]=d[l]={start:u,end:l,ring:[r,c]}}ja[l<<3].forEach(p)}(n,i,(function(t){r(t,n,i),function(t){for(var e=0,n=t.length,r=t[n-1][1]*t[0][0]-t[n-1][0]*t[0][1];++e0?a.push([t]):s.push(t)})),s.forEach((function(t){for(var e,n=0,r=a.length;n0&&o0&&s0&&a>0))throw new Error("invalid size");return t=r,e=a,i},i.thresholds=function(t){return arguments.length?(n="function"==typeof t?t:Array.isArray(t)?La(Oa.call(t)):La(t),i):n},i.smooth=function(t){return arguments.length?(r=t?s:Pa,i):r===s},i}function za(t,e,n){for(var r=t.width,i=t.height,a=1+(n<<1),o=0;o=n&&(s>=a&&(c-=t.data[s-a+o*r]),e.data[s-n+o*r]=c/Math.min(s+1,r-1+a-s,a))}function Ua(t,e,n){for(var r=t.width,i=t.height,a=1+(n<<1),o=0;o=n&&(s>=a&&(c-=t.data[o+(s-a)*r]),e.data[o+(s-n)*r]=c/Math.min(s+1,i-1+a-s,a))}function qa(t){return t[0]}function Ha(t){return t[1]}function $a(){return 1}function Wa(){var t=qa,e=Ha,n=$a,r=960,i=500,a=20,o=2,s=3*a,c=r+2*s>>o,u=i+2*s>>o,l=La(20);function h(r){var i=new Float32Array(c*u),h=new Float32Array(c*u);r.forEach((function(r,a,l){var h=+t(r,a,l)+s>>o,f=+e(r,a,l)+s>>o,d=+n(r,a,l);h>=0&&h=0&&f>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o),za({width:c,height:u,data:i},{width:c,height:u,data:h},a>>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o),za({width:c,height:u,data:i},{width:c,height:u,data:h},a>>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o);var d=l(i);if(!Array.isArray(d)){var p=I(i);d=M(0,p,d),(d=k(0,Math.floor(p/d)*d,d)).shift()}return Ya().thresholds(d).size([c,u])(i).map(f)}function f(t){return t.value*=Math.pow(2,-2*o),t.coordinates.forEach(d),t}function d(t){t.forEach(p)}function p(t){t.forEach(y)}function y(t){t[0]=t[0]*Math.pow(2,o)-s,t[1]=t[1]*Math.pow(2,o)-s}function g(){return c=r+2*(s=3*a)>>o,u=i+2*s>>o,h}return h.x=function(e){return arguments.length?(t="function"==typeof e?e:La(+e),h):t},h.y=function(t){return arguments.length?(e="function"==typeof t?t:La(+t),h):e},h.weight=function(t){return arguments.length?(n="function"==typeof t?t:La(+t),h):n},h.size=function(t){if(!arguments.length)return[r,i];var e=Math.ceil(t[0]),n=Math.ceil(t[1]);if(!(e>=0||e>=0))throw new Error("invalid size");return r=e,i=n,g()},h.cellSize=function(t){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return o=Math.floor(Math.log(t)/Math.LN2),g()},h.thresholds=function(t){return arguments.length?(l="function"==typeof t?t:Array.isArray(t)?La(Oa.call(t)):La(t),h):l},h.bandwidth=function(t){if(!arguments.length)return Math.sqrt(a*(a+1));if(!((t=+t)>=0))throw new Error("invalid bandwidth");return a=Math.round((Math.sqrt(4*t*t+1)-1)/2),g()},h}function Va(t){return function(){return t}}function Ga(t,e,n,r,i,a,o,s,c,u){this.target=t,this.type=e,this.subject=n,this.identifier=r,this.active=i,this.x=a,this.y=o,this.dx=s,this.dy=c,this._=u}function Xa(){return!ue.ctrlKey&&!ue.button}function Za(){return this.parentNode}function Ka(t){return null==t?{x:ue.x,y:ue.y}:t}function Qa(){return navigator.maxTouchPoints||"ontouchstart"in this}function Ja(){var t,e,n,r,i=Xa,a=Za,o=Ka,s=Qa,c={},u=ht("start","drag","end"),l=0,h=0;function f(t){t.on("mousedown.drag",d).filter(s).on("touchstart.drag",g).on("touchmove.drag",m).on("touchend.drag touchcancel.drag",v).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function d(){if(!r&&i.apply(this,arguments)){var o=b("mouse",a.apply(this,arguments),Dn,this,arguments);o&&(we(ue.view).on("mousemove.drag",p,!0).on("mouseup.drag",y,!0),Ee(ue.view),ke(),n=!1,t=ue.clientX,e=ue.clientY,o("start"))}}function p(){if(Te(),!n){var r=ue.clientX-t,i=ue.clientY-e;n=r*r+i*i>h}c.mouse("drag")}function y(){we(ue.view).on("mousemove.drag mouseup.drag",null),Ce(ue.view,n),Te(),c.mouse("end")}function g(){if(i.apply(this,arguments)){var t,e,n=ue.changedTouches,r=a.apply(this,arguments),o=n.length;for(t=0;t=a?c=!0:10===(r=t.charCodeAt(o++))?u=!0:13===r&&(u=!0,10===t.charCodeAt(o)&&++o),t.slice(i+1,e-1).replace(/""/g,'"')}for(;o9999?"+"+io(t,6):io(t,4)}(t.getUTCFullYear())+"-"+io(t.getUTCMonth()+1,2)+"-"+io(t.getUTCDate(),2)+(i?"T"+io(e,2)+":"+io(n,2)+":"+io(r,2)+"."+io(i,3)+"Z":r?"T"+io(e,2)+":"+io(n,2)+":"+io(r,2)+"Z":n||e?"T"+io(e,2)+":"+io(n,2)+"Z":"")}(t):e.test(t+="")?'"'+t.replace(/"/g,'""')+'"':t}return{parse:function(t,e){var n,i,a=r(t,(function(t,r){if(n)return n(t,r-1);i=t,n=e?function(t,e){var n=no(t);return function(r,i){return e(n(r),i,t)}}(t,e):no(t)}));return a.columns=i||[],a},parseRows:r,format:function(e,n){return null==n&&(n=ro(e)),[n.map(o).join(t)].concat(i(e,n)).join("\n")},formatBody:function(t,e){return null==e&&(e=ro(t)),i(t,e).join("\n")},formatRows:function(t){return t.map(a).join("\n")},formatRow:a,formatValue:o}}var oo=ao(","),so=oo.parse,co=oo.parseRows,uo=oo.format,lo=oo.formatBody,ho=oo.formatRows,fo=oo.formatRow,po=oo.formatValue,yo=ao("\t"),go=yo.parse,mo=yo.parseRows,vo=yo.format,bo=yo.formatBody,_o=yo.formatRows,xo=yo.formatRow,wo=yo.formatValue;function ko(t){for(var e in t){var n,r,i=t[e].trim();if(i)if("true"===i)i=!0;else if("false"===i)i=!1;else if("NaN"===i)i=NaN;else if(isNaN(n=+i)){if(!(r=i.match(/^([-+]\d{2})?\d{4}(-\d{2}(-\d{2})?)?(T\d{2}:\d{2}(:\d{2}(\.\d{3})?)?(Z|[-+]\d{2}:\d{2})?)?$/)))continue;To&&r[4]&&!r[7]&&(i=i.replace(/-/g,"/").replace(/T/," ")),i=new Date(i)}else i=n;else i=null;t[e]=i}return t}var To=new Date("2019-01-01T00:00").getHours()||new Date("2019-07-01T00:00").getHours();function Eo(t){return+t}function Co(t){return t*t}function So(t){return t*(2-t)}function Ao(t){return((t*=2)<=1?t*t:--t*(2-t)+1)/2}var Mo=function t(e){function n(t){return Math.pow(t,e)}return e=+e,n.exponent=t,n}(3),No=function t(e){function n(t){return 1-Math.pow(1-t,e)}return e=+e,n.exponent=t,n}(3),Do=function t(e){function n(t){return((t*=2)<=1?Math.pow(t,e):2-Math.pow(2-t,e))/2}return e=+e,n.exponent=t,n}(3),Oo=Math.PI,Bo=Oo/2;function Lo(t){return 1==+t?1:1-Math.cos(t*Bo)}function Io(t){return Math.sin(t*Bo)}function Ro(t){return(1-Math.cos(Oo*t))/2}function Fo(t){return 1.0009775171065494*(Math.pow(2,-10*t)-.0009765625)}function Po(t){return Fo(1-+t)}function jo(t){return 1-Fo(t)}function Yo(t){return((t*=2)<=1?Fo(1-t):2-Fo(t-1))/2}function zo(t){return 1-Math.sqrt(1-t*t)}function Uo(t){return Math.sqrt(1- --t*t)}function qo(t){return((t*=2)<=1?1-Math.sqrt(1-t*t):Math.sqrt(1-(t-=2)*t)+1)/2}var Ho=7.5625;function $o(t){return 1-Wo(1-t)}function Wo(t){return(t=+t)<.36363636363636365?Ho*t*t:t<.7272727272727273?Ho*(t-=.5454545454545454)*t+.75:t<.9090909090909091?Ho*(t-=.8181818181818182)*t+.9375:Ho*(t-=.9545454545454546)*t+.984375}function Vo(t){return((t*=2)<=1?1-Wo(1-t):Wo(t-1)+1)/2}var Go=1.70158,Xo=function t(e){function n(t){return(t=+t)*t*(e*(t-1)+t)}return e=+e,n.overshoot=t,n}(Go),Zo=function t(e){function n(t){return--t*t*((t+1)*e+t)+1}return e=+e,n.overshoot=t,n}(Go),Ko=function t(e){function n(t){return((t*=2)<1?t*t*((e+1)*t-e):(t-=2)*t*((e+1)*t+e)+2)/2}return e=+e,n.overshoot=t,n}(Go),Qo=2*Math.PI,Jo=function t(e,n){var r=Math.asin(1/(e=Math.max(1,e)))*(n/=Qo);function i(t){return e*Fo(- --t)*Math.sin((r-t)/n)}return i.amplitude=function(e){return t(e,n*Qo)},i.period=function(n){return t(e,n)},i}(1,.3),ts=function t(e,n){var r=Math.asin(1/(e=Math.max(1,e)))*(n/=Qo);function i(t){return 1-e*Fo(t=+t)*Math.sin((t+r)/n)}return i.amplitude=function(e){return t(e,n*Qo)},i.period=function(n){return t(e,n)},i}(1,.3),es=function t(e,n){var r=Math.asin(1/(e=Math.max(1,e)))*(n/=Qo);function i(t){return((t=2*t-1)<0?e*Fo(-t)*Math.sin((r-t)/n):2-e*Fo(t)*Math.sin((r+t)/n))/2}return i.amplitude=function(e){return t(e,n*Qo)},i.period=function(n){return t(e,n)},i}(1,.3);function ns(t){if(!t.ok)throw new Error(t.status+" "+t.statusText);return t.blob()}function rs(t,e){return fetch(t,e).then(ns)}function is(t){if(!t.ok)throw new Error(t.status+" "+t.statusText);return t.arrayBuffer()}function as(t,e){return fetch(t,e).then(is)}function os(t){if(!t.ok)throw new Error(t.status+" "+t.statusText);return t.text()}function ss(t,e){return fetch(t,e).then(os)}function cs(t){return function(e,n,r){return 2===arguments.length&&"function"==typeof n&&(r=n,n=void 0),ss(e,n).then((function(e){return t(e,r)}))}}function us(t,e,n,r){3===arguments.length&&"function"==typeof n&&(r=n,n=void 0);var i=ao(t);return ss(e,n).then((function(t){return i.parse(t,r)}))}var ls=cs(so),hs=cs(go);function fs(t,e){return new Promise((function(n,r){var i=new Image;for(var a in e)i[a]=e[a];i.onerror=r,i.onload=function(){n(i)},i.src=t}))}function ds(t){if(!t.ok)throw new Error(t.status+" "+t.statusText);if(204!==t.status&&205!==t.status)return t.json()}function ps(t,e){return fetch(t,e).then(ds)}function ys(t){return function(e,n){return ss(e,n).then((function(e){return(new DOMParser).parseFromString(e,t)}))}}const gs=ys("application/xml");var ms=ys("text/html"),vs=ys("image/svg+xml");function bs(t,e){var n;function r(){var r,i,a=n.length,o=0,s=0;for(r=0;r=(a=(y+m)/2))?y=a:m=a,(l=n>=(o=(g+v)/2))?g=o:v=o,i=d,!(d=d[h=l<<1|u]))return i[h]=p,t;if(s=+t._x.call(null,d.data),c=+t._y.call(null,d.data),e===s&&n===c)return p.next=d,i?i[h]=p:t._root=p,t;do{i=i?i[h]=new Array(4):t._root=new Array(4),(u=e>=(a=(y+m)/2))?y=a:m=a,(l=n>=(o=(g+v)/2))?g=o:v=o}while((h=l<<1|u)==(f=(c>=o)<<1|s>=a));return i[f]=d,i[h]=p,t}function ks(t,e,n,r,i){this.node=t,this.x0=e,this.y0=n,this.x1=r,this.y1=i}function Ts(t){return t[0]}function Es(t){return t[1]}function Cs(t,e,n){var r=new Ss(null==e?Ts:e,null==n?Es:n,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function Ss(t,e,n,r,i,a){this._x=t,this._y=e,this._x0=n,this._y0=r,this._x1=i,this._y1=a,this._root=void 0}function As(t){for(var e={data:t.data},n=e;t=t.next;)n=n.next={data:t.data};return e}var Ms=Cs.prototype=Ss.prototype;function Ns(t){return t.x+t.vx}function Ds(t){return t.y+t.vy}function Os(t){var e,n,r=1,i=1;function a(){for(var t,a,s,c,u,l,h,f=e.length,d=0;dc+d||iu+d||as.index){var p=c-o.x-o.vx,y=u-o.y-o.vy,g=p*p+y*y;gt.r&&(t.r=t[e].r)}function s(){if(e){var r,i,a=e.length;for(n=new Array(a),r=0;rl&&(l=r),ih&&(h=i));if(c>l||u>h)return this;for(this.cover(c,u).cover(l,h),n=0;nt||t>=i||r>e||e>=a;)switch(s=(ef||(a=c.y0)>d||(o=c.x1)=m)<<1|t>=g)&&(c=p[p.length-1],p[p.length-1]=p[p.length-1-u],p[p.length-1-u]=c)}else{var v=t-+this._x.call(null,y.data),b=e-+this._y.call(null,y.data),_=v*v+b*b;if(_=(s=(p+g)/2))?p=s:g=s,(l=o>=(c=(y+m)/2))?y=c:m=c,e=d,!(d=d[h=l<<1|u]))return this;if(!d.length)break;(e[h+1&3]||e[h+2&3]||e[h+3&3])&&(n=e,f=h)}for(;d.data!==t;)if(r=d,!(d=d.next))return this;return(i=d.next)&&delete d.next,r?(i?r.next=i:delete r.next,this):e?(i?e[h]=i:delete e[h],(d=e[0]||e[1]||e[2]||e[3])&&d===(e[3]||e[2]||e[1]||e[0])&&!d.length&&(n?n[f]=d:this._root=d),this):(this._root=i,this)},Ms.removeAll=function(t){for(var e=0,n=t.length;e1?(null==n?s.remove(t):s.set(t,d(n)),e):s.get(t)},find:function(e,n,r){var i,a,o,s,c,u=0,l=t.length;for(null==r?r=1/0:r*=r,u=0;u1?(u.on(t,n),e):u.on(t)}}}function Ys(){var t,e,n,r,i=_s(-30),a=1,o=1/0,s=.81;function c(r){var i,a=t.length,o=Cs(t,Rs,Fs).visitAfter(l);for(n=r,i=0;i=o)){(t.data!==e||t.next)&&(0===l&&(d+=(l=xs())*l),0===h&&(d+=(h=xs())*h),d1?r[0]+r.slice(2):r,+t.slice(n+1)]}function $s(t){return(t=Hs(Math.abs(t)))?t[1]:NaN}var Ws,Vs=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Gs(t){if(!(e=Vs.exec(t)))throw new Error("invalid format: "+t);var e;return new Xs({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}function Xs(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}function Zs(t,e){var n=Hs(t,e);if(!n)return t+"";var r=n[0],i=n[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}Gs.prototype=Xs.prototype,Xs.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};const Ks={"%":function(t,e){return(100*t).toFixed(e)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)},e:function(t,e){return t.toExponential(e)},f:function(t,e){return t.toFixed(e)},g:function(t,e){return t.toPrecision(e)},o:function(t){return Math.round(t).toString(8)},p:function(t,e){return Zs(100*t,e)},r:Zs,s:function(t,e){var n=Hs(t,e);if(!n)return t+"";var r=n[0],i=n[1],a=i-(Ws=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,o=r.length;return a===o?r:a>o?r+new Array(a-o+1).join("0"):a>0?r.slice(0,a)+"."+r.slice(a):"0."+new Array(1-a).join("0")+Hs(t,Math.max(0,e+a-1))[0]},X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}};function Qs(t){return t}var Js,tc,ec,nc=Array.prototype.map,rc=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"];function ic(t){var e,n,r=void 0===t.grouping||void 0===t.thousands?Qs:(e=nc.call(t.grouping,Number),n=t.thousands+"",function(t,r){for(var i=t.length,a=[],o=0,s=e[0],c=0;i>0&&s>0&&(c+s+1>r&&(s=Math.max(1,r-c)),a.push(t.substring(i-=s,i+s)),!((c+=s+1)>r));)s=e[o=(o+1)%e.length];return a.reverse().join(n)}),i=void 0===t.currency?"":t.currency[0]+"",a=void 0===t.currency?"":t.currency[1]+"",o=void 0===t.decimal?".":t.decimal+"",s=void 0===t.numerals?Qs:function(t){return function(e){return e.replace(/[0-9]/g,(function(e){return t[+e]}))}}(nc.call(t.numerals,String)),c=void 0===t.percent?"%":t.percent+"",u=void 0===t.minus?"-":t.minus+"",l=void 0===t.nan?"NaN":t.nan+"";function h(t){var e=(t=Gs(t)).fill,n=t.align,h=t.sign,f=t.symbol,d=t.zero,p=t.width,y=t.comma,g=t.precision,m=t.trim,v=t.type;"n"===v?(y=!0,v="g"):Ks[v]||(void 0===g&&(g=12),m=!0,v="g"),(d||"0"===e&&"="===n)&&(d=!0,e="0",n="=");var b="$"===f?i:"#"===f&&/[boxX]/.test(v)?"0"+v.toLowerCase():"",_="$"===f?a:/[%p]/.test(v)?c:"",x=Ks[v],w=/[defgprs%]/.test(v);function k(t){var i,a,c,f=b,k=_;if("c"===v)k=x(t)+k,t="";else{var T=(t=+t)<0||1/t<0;if(t=isNaN(t)?l:x(Math.abs(t),g),m&&(t=function(t){t:for(var e,n=t.length,r=1,i=-1;r0&&(i=0)}return i>0?t.slice(0,i)+t.slice(e+1):t}(t)),T&&0==+t&&"+"!==h&&(T=!1),f=(T?"("===h?h:u:"-"===h||"("===h?"":h)+f,k=("s"===v?rc[8+Ws/3]:"")+k+(T&&"("===h?")":""),w)for(i=-1,a=t.length;++i(c=t.charCodeAt(i))||c>57){k=(46===c?o+t.slice(i+1):t.slice(i))+k,t=t.slice(0,i);break}}y&&!d&&(t=r(t,1/0));var E=f.length+t.length+k.length,C=E>1)+f+t+k+C.slice(E);break;default:t=C+f+t+k}return s(t)}return g=void 0===g?6:/[gprs]/.test(v)?Math.max(1,Math.min(21,g)):Math.max(0,Math.min(20,g)),k.toString=function(){return t+""},k}return{format:h,formatPrefix:function(t,e){var n=h(((t=Gs(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor($s(e)/3))),i=Math.pow(10,-r),a=rc[8+r/3];return function(t){return n(i*t)+a}}}}function ac(t){return Js=ic(t),tc=Js.format,ec=Js.formatPrefix,Js}function oc(t){return Math.max(0,-$s(Math.abs(t)))}function sc(t,e){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor($s(e)/3)))-$s(Math.abs(t)))}function cc(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,$s(e)-$s(t))+1}function uc(){return new lc}function lc(){this.reset()}ac({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"}),lc.prototype={constructor:lc,reset:function(){this.s=this.t=0},add:function(t){fc(hc,t,this.t),fc(this,hc.s,this.s),this.s?this.t+=hc.t:this.s=hc.t},valueOf:function(){return this.s}};var hc=new lc;function fc(t,e,n){var r=t.s=e+n,i=r-e,a=r-i;t.t=e-a+(n-i)}var dc=1e-6,pc=1e-12,yc=Math.PI,gc=yc/2,mc=yc/4,vc=2*yc,bc=180/yc,_c=yc/180,xc=Math.abs,wc=Math.atan,kc=Math.atan2,Tc=Math.cos,Ec=Math.ceil,Cc=Math.exp,Sc=(Math.floor,Math.log),Ac=Math.pow,Mc=Math.sin,Nc=Math.sign||function(t){return t>0?1:t<0?-1:0},Dc=Math.sqrt,Oc=Math.tan;function Bc(t){return t>1?0:t<-1?yc:Math.acos(t)}function Lc(t){return t>1?gc:t<-1?-gc:Math.asin(t)}function Ic(t){return(t=Mc(t/2))*t}function Rc(){}function Fc(t,e){t&&jc.hasOwnProperty(t.type)&&jc[t.type](t,e)}var Pc={Feature:function(t,e){Fc(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r=0?1:-1,i=r*n,a=Tc(e=(e*=_c)/2+mc),o=Mc(e),s=Vc*o,c=Wc*a+s*Tc(i),u=s*r*Mc(i);Gc.add(kc(u,c)),$c=t,Wc=a,Vc=o}function eu(t){return Xc.reset(),Uc(t,Zc),2*Xc}function nu(t){return[kc(t[1],t[0]),Lc(t[2])]}function ru(t){var e=t[0],n=t[1],r=Tc(n);return[r*Tc(e),r*Mc(e),Mc(n)]}function iu(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}function au(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function ou(t,e){t[0]+=e[0],t[1]+=e[1],t[2]+=e[2]}function su(t,e){return[t[0]*e,t[1]*e,t[2]*e]}function cu(t){var e=Dc(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=e,t[1]/=e,t[2]/=e}var uu,lu,hu,fu,du,pu,yu,gu,mu,vu,bu,_u,xu,wu,ku,Tu,Eu,Cu,Su,Au,Mu,Nu,Du,Ou,Bu,Lu,Iu=uc(),Ru={point:Fu,lineStart:ju,lineEnd:Yu,polygonStart:function(){Ru.point=zu,Ru.lineStart=Uu,Ru.lineEnd=qu,Iu.reset(),Zc.polygonStart()},polygonEnd:function(){Zc.polygonEnd(),Ru.point=Fu,Ru.lineStart=ju,Ru.lineEnd=Yu,Gc<0?(uu=-(hu=180),lu=-(fu=90)):Iu>dc?fu=90:Iu<-1e-6&&(lu=-90),vu[0]=uu,vu[1]=hu},sphere:function(){uu=-(hu=180),lu=-(fu=90)}};function Fu(t,e){mu.push(vu=[uu=t,hu=t]),efu&&(fu=e)}function Pu(t,e){var n=ru([t*_c,e*_c]);if(gu){var r=au(gu,n),i=au([r[1],-r[0],0],r);cu(i),i=nu(i);var a,o=t-du,s=o>0?1:-1,c=i[0]*bc*s,u=xc(o)>180;u^(s*dufu&&(fu=a):u^(s*du<(c=(c+360)%360-180)&&cfu&&(fu=e)),u?tHu(uu,hu)&&(hu=t):Hu(t,hu)>Hu(uu,hu)&&(uu=t):hu>=uu?(thu&&(hu=t)):t>du?Hu(uu,t)>Hu(uu,hu)&&(hu=t):Hu(t,hu)>Hu(uu,hu)&&(uu=t)}else mu.push(vu=[uu=t,hu=t]);efu&&(fu=e),gu=n,du=t}function ju(){Ru.point=Pu}function Yu(){vu[0]=uu,vu[1]=hu,Ru.point=Fu,gu=null}function zu(t,e){if(gu){var n=t-du;Iu.add(xc(n)>180?n+(n>0?360:-360):n)}else pu=t,yu=e;Zc.point(t,e),Pu(t,e)}function Uu(){Zc.lineStart()}function qu(){zu(pu,yu),Zc.lineEnd(),xc(Iu)>dc&&(uu=-(hu=180)),vu[0]=uu,vu[1]=hu,gu=null}function Hu(t,e){return(e-=t)<0?e+360:e}function $u(t,e){return t[0]-e[0]}function Wu(t,e){return t[0]<=t[1]?t[0]<=e&&e<=t[1]:eHu(r[0],r[1])&&(r[1]=i[1]),Hu(i[0],r[1])>Hu(r[0],r[1])&&(r[0]=i[0])):a.push(r=i);for(o=-1/0,e=0,r=a[n=a.length-1];e<=n;r=i,++e)i=a[e],(s=Hu(r[1],i[0]))>o&&(o=s,uu=i[0],hu=r[1])}return mu=vu=null,uu===1/0||lu===1/0?[[NaN,NaN],[NaN,NaN]]:[[uu,lu],[hu,fu]]}var Gu={sphere:Rc,point:Xu,lineStart:Ku,lineEnd:tl,polygonStart:function(){Gu.lineStart=el,Gu.lineEnd=nl},polygonEnd:function(){Gu.lineStart=Ku,Gu.lineEnd=tl}};function Xu(t,e){t*=_c;var n=Tc(e*=_c);Zu(n*Tc(t),n*Mc(t),Mc(e))}function Zu(t,e,n){++bu,xu+=(t-xu)/bu,wu+=(e-wu)/bu,ku+=(n-ku)/bu}function Ku(){Gu.point=Qu}function Qu(t,e){t*=_c;var n=Tc(e*=_c);Ou=n*Tc(t),Bu=n*Mc(t),Lu=Mc(e),Gu.point=Ju,Zu(Ou,Bu,Lu)}function Ju(t,e){t*=_c;var n=Tc(e*=_c),r=n*Tc(t),i=n*Mc(t),a=Mc(e),o=kc(Dc((o=Bu*a-Lu*i)*o+(o=Lu*r-Ou*a)*o+(o=Ou*i-Bu*r)*o),Ou*r+Bu*i+Lu*a);_u+=o,Tu+=o*(Ou+(Ou=r)),Eu+=o*(Bu+(Bu=i)),Cu+=o*(Lu+(Lu=a)),Zu(Ou,Bu,Lu)}function tl(){Gu.point=Xu}function el(){Gu.point=rl}function nl(){il(Nu,Du),Gu.point=Xu}function rl(t,e){Nu=t,Du=e,t*=_c,e*=_c,Gu.point=il;var n=Tc(e);Ou=n*Tc(t),Bu=n*Mc(t),Lu=Mc(e),Zu(Ou,Bu,Lu)}function il(t,e){t*=_c;var n=Tc(e*=_c),r=n*Tc(t),i=n*Mc(t),a=Mc(e),o=Bu*a-Lu*i,s=Lu*r-Ou*a,c=Ou*i-Bu*r,u=Dc(o*o+s*s+c*c),l=Lc(u),h=u&&-l/u;Su+=h*o,Au+=h*s,Mu+=h*c,_u+=l,Tu+=l*(Ou+(Ou=r)),Eu+=l*(Bu+(Bu=i)),Cu+=l*(Lu+(Lu=a)),Zu(Ou,Bu,Lu)}function al(t){bu=_u=xu=wu=ku=Tu=Eu=Cu=Su=Au=Mu=0,Uc(t,Gu);var e=Su,n=Au,r=Mu,i=e*e+n*n+r*r;return iyc?t+Math.round(-t/vc)*vc:t,e]}function ul(t,e,n){return(t%=vc)?e||n?sl(hl(t),fl(e,n)):hl(t):e||n?fl(e,n):cl}function ll(t){return function(e,n){return[(e+=t)>yc?e-vc:e<-yc?e+vc:e,n]}}function hl(t){var e=ll(t);return e.invert=ll(-t),e}function fl(t,e){var n=Tc(t),r=Mc(t),i=Tc(e),a=Mc(e);function o(t,e){var o=Tc(e),s=Tc(t)*o,c=Mc(t)*o,u=Mc(e),l=u*n+s*r;return[kc(c*i-l*a,s*n-u*r),Lc(l*i+c*a)]}return o.invert=function(t,e){var o=Tc(e),s=Tc(t)*o,c=Mc(t)*o,u=Mc(e),l=u*i-c*a;return[kc(c*i+u*a,s*n+l*r),Lc(l*n-s*r)]},o}function dl(t){function e(e){return(e=t(e[0]*_c,e[1]*_c))[0]*=bc,e[1]*=bc,e}return t=ul(t[0]*_c,t[1]*_c,t.length>2?t[2]*_c:0),e.invert=function(e){return(e=t.invert(e[0]*_c,e[1]*_c))[0]*=bc,e[1]*=bc,e},e}function pl(t,e,n,r,i,a){if(n){var o=Tc(e),s=Mc(e),c=r*n;null==i?(i=e+r*vc,a=e-c/2):(i=yl(o,i),a=yl(o,a),(r>0?ia)&&(i+=r*vc));for(var u,l=i;r>0?l>a:l1&&e.push(e.pop().concat(e.shift()))},result:function(){var n=e;return e=[],t=null,n}}}function vl(t,e){return xc(t[0]-e[0])=0;--a)i.point((l=u[a])[0],l[1]);else r(f.x,f.p.x,-1,i);f=f.p}u=(f=f.o).z,d=!d}while(!f.v);i.lineEnd()}}}function xl(t){if(e=t.length){for(var e,n,r=0,i=t[0];++r=0?1:-1,E=T*k,C=E>yc,S=y*x;if(wl.add(kc(S*T*Mc(E),g*w+S*Tc(E))),o+=C?k+T*vc:k,C^d>=n^b>=n){var A=au(ru(f),ru(v));cu(A);var M=au(a,A);cu(M);var N=(C^k>=0?-1:1)*Lc(M[2]);(r>N||r===N&&(A[0]||A[1]))&&(s+=C^k>=0?1:-1)}}return(o<-1e-6||o0){for(h||(i.polygonStart(),h=!0),i.lineStart(),t=0;t1&&2&c&&f.push(f.pop().concat(f.shift())),o.push(f.filter(Cl))}return f}}function Cl(t){return t.length>1}function Sl(t,e){return((t=t.x)[0]<0?t[1]-gc-dc:gc-t[1])-((e=e.x)[0]<0?e[1]-gc-dc:gc-e[1])}const Al=El((function(){return!0}),(function(t){var e,n=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),e=1},point:function(a,o){var s=a>0?yc:-yc,c=xc(a-n);xc(c-yc)0?gc:-gc),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),t.point(a,r),e=0):i!==s&&c>=yc&&(xc(n-i)dc?wc((Mc(e)*(a=Tc(r))*Mc(n)-Mc(r)*(i=Tc(e))*Mc(t))/(i*a*o)):(e+r)/2}(n,r,a,o),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),e=0),t.point(n=a,r=o),i=s},lineEnd:function(){t.lineEnd(),n=r=NaN},clean:function(){return 2-e}}}),(function(t,e,n,r){var i;if(null==t)i=n*gc,r.point(-yc,i),r.point(0,i),r.point(yc,i),r.point(yc,0),r.point(yc,-i),r.point(0,-i),r.point(-yc,-i),r.point(-yc,0),r.point(-yc,i);else if(xc(t[0]-e[0])>dc){var a=t[0]0,i=xc(e)>dc;function a(t,n){return Tc(t)*Tc(n)>e}function o(t,n,r){var i=[1,0,0],a=au(ru(t),ru(n)),o=iu(a,a),s=a[0],c=o-s*s;if(!c)return!r&&t;var u=e*o/c,l=-e*s/c,h=au(i,a),f=su(i,u);ou(f,su(a,l));var d=h,p=iu(f,d),y=iu(d,d),g=p*p-y*(iu(f,f)-1);if(!(g<0)){var m=Dc(g),v=su(d,(-p-m)/y);if(ou(v,f),v=nu(v),!r)return v;var b,_=t[0],x=n[0],w=t[1],k=n[1];x<_&&(b=_,_=x,x=b);var T=x-_,E=xc(T-yc)0^v[1]<(xc(v[0]-_)yc^(_<=v[0]&&v[0]<=x)){var C=su(d,(-p+m)/y);return ou(C,f),[v,nu(C)]}}}function s(e,n){var i=r?t:yc-t,a=0;return e<-i?a|=1:e>i&&(a|=2),n<-i?a|=4:n>i&&(a|=8),a}return El(a,(function(t){var e,n,c,u,l;return{lineStart:function(){u=c=!1,l=1},point:function(h,f){var d,p=[h,f],y=a(h,f),g=r?y?0:s(h,f):y?s(h+(h<0?yc:-yc),f):0;if(!e&&(u=c=y)&&t.lineStart(),y!==c&&(!(d=o(e,p))||vl(e,d)||vl(p,d))&&(p[2]=1),y!==c)l=0,y?(t.lineStart(),d=o(p,e),t.point(d[0],d[1])):(d=o(e,p),t.point(d[0],d[1],2),t.lineEnd()),e=d;else if(i&&e&&r^y){var m;g&n||!(m=o(p,e,!0))||(l=0,r?(t.lineStart(),t.point(m[0][0],m[0][1]),t.point(m[1][0],m[1][1]),t.lineEnd()):(t.point(m[1][0],m[1][1]),t.lineEnd(),t.lineStart(),t.point(m[0][0],m[0][1],3)))}!y||e&&vl(e,p)||t.point(p[0],p[1]),e=p,c=y,n=g},lineEnd:function(){c&&t.lineEnd(),e=null},clean:function(){return l|(u&&c)<<1}}}),(function(e,r,i,a){pl(a,t,n,i,e,r)}),r?[0,-t]:[-yc,t-yc])}var Nl=1e9,Dl=-Nl;function Ol(t,e,n,r){function i(i,a){return t<=i&&i<=n&&e<=a&&a<=r}function a(i,a,s,u){var l=0,h=0;if(null==i||(l=o(i,s))!==(h=o(a,s))||c(i,a)<0^s>0)do{u.point(0===l||3===l?t:n,l>1?r:e)}while((l=(l+s+4)%4)!==h);else u.point(a[0],a[1])}function o(r,i){return xc(r[0]-t)0?0:3:xc(r[0]-n)0?2:1:xc(r[1]-e)0?1:0:i>0?3:2}function s(t,e){return c(t.x,e.x)}function c(t,e){var n=o(t,1),r=o(e,1);return n!==r?n-r:0===n?e[1]-t[1]:1===n?t[0]-e[0]:2===n?t[1]-e[1]:e[0]-t[0]}return function(o){var c,u,l,h,f,d,p,y,g,m,v,b=o,_=ml(),x={point:w,lineStart:function(){x.point=k,u&&u.push(l=[]),m=!0,g=!1,p=y=NaN},lineEnd:function(){c&&(k(h,f),d&&g&&_.rejoin(),c.push(_.result())),x.point=w,g&&b.lineEnd()},polygonStart:function(){b=_,c=[],u=[],v=!0},polygonEnd:function(){var e=function(){for(var e=0,n=0,i=u.length;nr&&(f-a)*(r-o)>(d-o)*(t-a)&&++e:d<=r&&(f-a)*(r-o)<(d-o)*(t-a)&&--e;return e}(),n=v&&e,i=(c=P(c)).length;(n||i)&&(o.polygonStart(),n&&(o.lineStart(),a(null,null,1,o),o.lineEnd()),i&&_l(c,s,e,a,o),o.polygonEnd()),b=o,c=u=l=null}};function w(t,e){i(t,e)&&b.point(t,e)}function k(a,o){var s=i(a,o);if(u&&l.push([a,o]),m)h=a,f=o,d=s,m=!1,s&&(b.lineStart(),b.point(a,o));else if(s&&g)b.point(a,o);else{var c=[p=Math.max(Dl,Math.min(Nl,p)),y=Math.max(Dl,Math.min(Nl,y))],_=[a=Math.max(Dl,Math.min(Nl,a)),o=Math.max(Dl,Math.min(Nl,o))];!function(t,e,n,r,i,a){var o,s=t[0],c=t[1],u=0,l=1,h=e[0]-s,f=e[1]-c;if(o=n-s,h||!(o>0)){if(o/=h,h<0){if(o0){if(o>l)return;o>u&&(u=o)}if(o=i-s,h||!(o<0)){if(o/=h,h<0){if(o>l)return;o>u&&(u=o)}else if(h>0){if(o0)){if(o/=f,f<0){if(o0){if(o>l)return;o>u&&(u=o)}if(o=a-c,f||!(o<0)){if(o/=f,f<0){if(o>l)return;o>u&&(u=o)}else if(f>0){if(o0&&(t[0]=s+u*h,t[1]=c+u*f),l<1&&(e[0]=s+l*h,e[1]=c+l*f),!0}}}}}(c,_,t,e,n,r)?s&&(b.lineStart(),b.point(a,o),v=!1):(g||(b.lineStart(),b.point(c[0],c[1])),b.point(_[0],_[1]),s||b.lineEnd(),v=!1)}p=a,y=o,g=s}return x}}function Bl(){var t,e,n,r=0,i=0,a=960,o=500;return n={stream:function(n){return t&&e===n?t:t=Ol(r,i,a,o)(e=n)},extent:function(s){return arguments.length?(r=+s[0][0],i=+s[0][1],a=+s[1][0],o=+s[1][1],t=e=null,n):[[r,i],[a,o]]}}}var Ll,Il,Rl,Fl=uc(),Pl={sphere:Rc,point:Rc,lineStart:function(){Pl.point=Yl,Pl.lineEnd=jl},lineEnd:Rc,polygonStart:Rc,polygonEnd:Rc};function jl(){Pl.point=Pl.lineEnd=Rc}function Yl(t,e){Ll=t*=_c,Il=Mc(e*=_c),Rl=Tc(e),Pl.point=zl}function zl(t,e){t*=_c;var n=Mc(e*=_c),r=Tc(e),i=xc(t-Ll),a=Tc(i),o=r*Mc(i),s=Rl*n-Il*r*a,c=Il*n+Rl*r*a;Fl.add(kc(Dc(o*o+s*s),c)),Ll=t,Il=n,Rl=r}function Ul(t){return Fl.reset(),Uc(t,Pl),+Fl}var ql=[null,null],Hl={type:"LineString",coordinates:ql};function $l(t,e){return ql[0]=t,ql[1]=e,Ul(Hl)}var Wl={Feature:function(t,e){return Gl(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r0&&(i=$l(t[a],t[a-1]))>0&&n<=i&&r<=i&&(n+r-i)*(1-Math.pow((n-r)/i,2))dc})).map(c)).concat(k(Ec(a/d)*d,i,d).filter((function(t){return xc(t%y)>dc})).map(u))}return m.lines=function(){return v().map((function(t){return{type:"LineString",coordinates:t}}))},m.outline=function(){return{type:"Polygon",coordinates:[l(r).concat(h(o).slice(1),l(n).reverse().slice(1),h(s).reverse().slice(1))]}},m.extent=function(t){return arguments.length?m.extentMajor(t).extentMinor(t):m.extentMinor()},m.extentMajor=function(t){return arguments.length?(r=+t[0][0],n=+t[1][0],s=+t[0][1],o=+t[1][1],r>n&&(t=r,r=n,n=t),s>o&&(t=s,s=o,o=t),m.precision(g)):[[r,s],[n,o]]},m.extentMinor=function(n){return arguments.length?(e=+n[0][0],t=+n[1][0],a=+n[0][1],i=+n[1][1],e>t&&(n=e,e=t,t=n),a>i&&(n=a,a=i,i=n),m.precision(g)):[[e,a],[t,i]]},m.step=function(t){return arguments.length?m.stepMajor(t).stepMinor(t):m.stepMinor()},m.stepMajor=function(t){return arguments.length?(p=+t[0],y=+t[1],m):[p,y]},m.stepMinor=function(t){return arguments.length?(f=+t[0],d=+t[1],m):[f,d]},m.precision=function(f){return arguments.length?(g=+f,c=eh(a,i,90),u=nh(e,t,g),l=eh(s,o,90),h=nh(r,n,g),m):g},m.extentMajor([[-180,-89.999999],[180,89.999999]]).extentMinor([[-180,-80.000001],[180,80.000001]])}function ih(){return rh()()}function ah(t,e){var n=t[0]*_c,r=t[1]*_c,i=e[0]*_c,a=e[1]*_c,o=Tc(r),s=Mc(r),c=Tc(a),u=Mc(a),l=o*Tc(n),h=o*Mc(n),f=c*Tc(i),d=c*Mc(i),p=2*Lc(Dc(Ic(a-r)+o*c*Ic(i-n))),y=Mc(p),g=p?function(t){var e=Mc(t*=p)/y,n=Mc(p-t)/y,r=n*l+e*f,i=n*h+e*d,a=n*s+e*u;return[kc(i,r)*bc,kc(a,Dc(r*r+i*i))*bc]}:function(){return[n*bc,r*bc]};return g.distance=p,g}function oh(t){return t}var sh,ch,uh,lh,hh=uc(),fh=uc(),dh={point:Rc,lineStart:Rc,lineEnd:Rc,polygonStart:function(){dh.lineStart=ph,dh.lineEnd=mh},polygonEnd:function(){dh.lineStart=dh.lineEnd=dh.point=Rc,hh.add(xc(fh)),fh.reset()},result:function(){var t=hh/2;return hh.reset(),t}};function ph(){dh.point=yh}function yh(t,e){dh.point=gh,sh=uh=t,ch=lh=e}function gh(t,e){fh.add(lh*t-uh*e),uh=t,lh=e}function mh(){gh(sh,ch)}const vh=dh;var bh=1/0,_h=bh,xh=-bh,wh=xh,kh={point:function(t,e){txh&&(xh=t),e<_h&&(_h=e),e>wh&&(wh=e)},lineStart:Rc,lineEnd:Rc,polygonStart:Rc,polygonEnd:Rc,result:function(){var t=[[bh,_h],[xh,wh]];return xh=wh=-(_h=bh=1/0),t}};const Th=kh;var Eh,Ch,Sh,Ah,Mh=0,Nh=0,Dh=0,Oh=0,Bh=0,Lh=0,Ih=0,Rh=0,Fh=0,Ph={point:jh,lineStart:Yh,lineEnd:qh,polygonStart:function(){Ph.lineStart=Hh,Ph.lineEnd=$h},polygonEnd:function(){Ph.point=jh,Ph.lineStart=Yh,Ph.lineEnd=qh},result:function(){var t=Fh?[Ih/Fh,Rh/Fh]:Lh?[Oh/Lh,Bh/Lh]:Dh?[Mh/Dh,Nh/Dh]:[NaN,NaN];return Mh=Nh=Dh=Oh=Bh=Lh=Ih=Rh=Fh=0,t}};function jh(t,e){Mh+=t,Nh+=e,++Dh}function Yh(){Ph.point=zh}function zh(t,e){Ph.point=Uh,jh(Sh=t,Ah=e)}function Uh(t,e){var n=t-Sh,r=e-Ah,i=Dc(n*n+r*r);Oh+=i*(Sh+t)/2,Bh+=i*(Ah+e)/2,Lh+=i,jh(Sh=t,Ah=e)}function qh(){Ph.point=jh}function Hh(){Ph.point=Wh}function $h(){Vh(Eh,Ch)}function Wh(t,e){Ph.point=Vh,jh(Eh=Sh=t,Ch=Ah=e)}function Vh(t,e){var n=t-Sh,r=e-Ah,i=Dc(n*n+r*r);Oh+=i*(Sh+t)/2,Bh+=i*(Ah+e)/2,Lh+=i,Ih+=(i=Ah*t-Sh*e)*(Sh+t),Rh+=i*(Ah+e),Fh+=3*i,jh(Sh=t,Ah=e)}const Gh=Ph;function Xh(t){this._context=t}Xh.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._context.moveTo(t,e),this._point=1;break;case 1:this._context.lineTo(t,e);break;default:this._context.moveTo(t+this._radius,e),this._context.arc(t,e,this._radius,0,vc)}},result:Rc};var Zh,Kh,Qh,Jh,tf,ef=uc(),nf={point:Rc,lineStart:function(){nf.point=rf},lineEnd:function(){Zh&&af(Kh,Qh),nf.point=Rc},polygonStart:function(){Zh=!0},polygonEnd:function(){Zh=null},result:function(){var t=+ef;return ef.reset(),t}};function rf(t,e){nf.point=af,Kh=Jh=t,Qh=tf=e}function af(t,e){Jh-=t,tf-=e,ef.add(Dc(Jh*Jh+tf*tf)),Jh=t,tf=e}const of=nf;function sf(){this._string=[]}function cf(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}function uf(t,e){var n,r,i=4.5;function a(t){return t&&("function"==typeof i&&r.pointRadius(+i.apply(this,arguments)),Uc(t,n(r))),r.result()}return a.area=function(t){return Uc(t,n(vh)),vh.result()},a.measure=function(t){return Uc(t,n(of)),of.result()},a.bounds=function(t){return Uc(t,n(Th)),Th.result()},a.centroid=function(t){return Uc(t,n(Gh)),Gh.result()},a.projection=function(e){return arguments.length?(n=null==e?(t=null,oh):(t=e).stream,a):t},a.context=function(t){return arguments.length?(r=null==t?(e=null,new sf):new Xh(e=t),"function"!=typeof i&&r.pointRadius(i),a):e},a.pointRadius=function(t){return arguments.length?(i="function"==typeof t?t:(r.pointRadius(+t),+t),a):i},a.projection(t).context(e)}function lf(t){return{stream:hf(t)}}function hf(t){return function(e){var n=new ff;for(var r in t)n[r]=t[r];return n.stream=e,n}}function ff(){}function df(t,e,n){var r=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),null!=r&&t.clipExtent(null),Uc(n,t.stream(Th)),e(Th.result()),null!=r&&t.clipExtent(r),t}function pf(t,e,n){return df(t,(function(n){var r=e[1][0]-e[0][0],i=e[1][1]-e[0][1],a=Math.min(r/(n[1][0]-n[0][0]),i/(n[1][1]-n[0][1])),o=+e[0][0]+(r-a*(n[1][0]+n[0][0]))/2,s=+e[0][1]+(i-a*(n[1][1]+n[0][1]))/2;t.scale(150*a).translate([o,s])}),n)}function yf(t,e,n){return pf(t,[[0,0],e],n)}function gf(t,e,n){return df(t,(function(n){var r=+e,i=r/(n[1][0]-n[0][0]),a=(r-i*(n[1][0]+n[0][0]))/2,o=-i*n[0][1];t.scale(150*i).translate([a,o])}),n)}function mf(t,e,n){return df(t,(function(n){var r=+e,i=r/(n[1][1]-n[0][1]),a=-i*n[0][0],o=(r-i*(n[1][1]+n[0][1]))/2;t.scale(150*i).translate([a,o])}),n)}sf.prototype={_radius:4.5,_circle:cf(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._string.push("M",t,",",e),this._point=1;break;case 1:this._string.push("L",t,",",e);break;default:null==this._circle&&(this._circle=cf(this._radius)),this._string.push("M",t,",",e,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}return null}},ff.prototype={constructor:ff,point:function(t,e){this.stream.point(t,e)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var vf=Tc(30*_c);function bf(t,e){return+e?function(t,e){function n(r,i,a,o,s,c,u,l,h,f,d,p,y,g){var m=u-r,v=l-i,b=m*m+v*v;if(b>4*e&&y--){var _=o+f,x=s+d,w=c+p,k=Dc(_*_+x*x+w*w),T=Lc(w/=k),E=xc(xc(w)-1)e||xc((m*M+v*N)/b-.5)>.3||o*f+s*d+c*p2?t[2]%360*_c:0,M()):[g*bc,m*bc,v*bc]},S.angle=function(t){return arguments.length?(b=t%360*_c,M()):b*bc},S.reflectX=function(t){return arguments.length?(_=t?-1:1,M()):_<0},S.reflectY=function(t){return arguments.length?(x=t?-1:1,M()):x<0},S.precision=function(t){return arguments.length?(o=bf(s,C=t*t),N()):Dc(C)},S.fitExtent=function(t,e){return pf(S,t,e)},S.fitSize=function(t,e){return yf(S,t,e)},S.fitWidth=function(t,e){return gf(S,t,e)},S.fitHeight=function(t,e){return mf(S,t,e)},function(){return e=t.apply(this,arguments),S.invert=e.invert&&A,M()}}function Ef(t){var e=0,n=yc/3,r=Tf(t),i=r(e,n);return i.parallels=function(t){return arguments.length?r(e=t[0]*_c,n=t[1]*_c):[e*bc,n*bc]},i}function Cf(t,e){var n=Mc(t),r=(n+Mc(e))/2;if(xc(r)=.12&&i<.234&&r>=-.425&&r<-.214?s:i>=.166&&i<.234&&r>=-.214&&r<-.115?c:o).invert(t)},l.stream=function(n){return t&&e===n?t:(r=[o.stream(e=n),s.stream(n),c.stream(n)],i=r.length,t={point:function(t,e){for(var n=-1;++n0?e<-gc+dc&&(e=-gc+dc):e>gc-dc&&(e=gc-dc);var n=i/Ac(jf(e),r);return[n*Mc(r*t),i-n*Tc(r*t)]}return a.invert=function(t,e){var n=i-e,a=Nc(r)*Dc(t*t+n*n),o=kc(t,xc(n))*Nc(n);return n*r<0&&(o-=yc*Nc(t)*Nc(n)),[o/r,2*wc(Ac(i/a,1/r))-gc]},a}function zf(){return Ef(Yf).scale(109.5).parallels([30,30])}function Uf(t,e){return[t,e]}function qf(){return kf(Uf).scale(152.63)}function Hf(t,e){var n=Tc(t),r=t===e?Mc(t):(n-Tc(e))/(e-t),i=n/r+t;if(xc(r)2?t[2]+90:90]):[(t=n())[0],t[1],t[2]-90]},n([0,0,90]).scale(159.155)}function ld(t,e){return t.parent===e.parent?1:2}function hd(t,e){return t+e.x}function fd(t,e){return Math.max(t,e.y)}function dd(){var t=ld,e=1,n=1,r=!1;function i(i){var a,o=0;i.eachAfter((function(e){var n=e.children;n?(e.x=function(t){return t.reduce(hd,0)/t.length}(n),e.y=function(t){return 1+t.reduce(fd,0)}(n)):(e.x=a?o+=t(e,a):0,e.y=0,a=e)}));var s=function(t){for(var e;e=t.children;)t=e[0];return t}(i),c=function(t){for(var e;e=t.children;)t=e[e.length-1];return t}(i),u=s.x-t(s,c)/2,l=c.x+t(c,s)/2;return i.eachAfter(r?function(t){t.x=(t.x-i.x)*e,t.y=(i.y-t.y)*n}:function(t){t.x=(t.x-u)/(l-u)*e,t.y=(1-(i.y?t.y/i.y:1))*n})}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i}function pd(t){var e=0,n=t.children,r=n&&n.length;if(r)for(;--r>=0;)e+=n[r].value;else e=1;t.value=e}function yd(t,e){var n,r,i,a,o,s=new bd(t),c=+t.value&&(s.value=t.value),u=[s];for(null==e&&(e=gd);n=u.pop();)if(c&&(n.value=+n.data.value),(i=e(n.data))&&(o=i.length))for(n.children=new Array(o),a=o-1;a>=0;--a)u.push(r=n.children[a]=new bd(i[a])),r.parent=n,r.depth=n.depth+1;return s.eachBefore(vd)}function gd(t){return t.children}function md(t){t.data=t.data.data}function vd(t){var e=0;do{t.height=e}while((t=t.parent)&&t.height<++e)}function bd(t){this.data=t,this.depth=this.height=0,this.parent=null}Kf.invert=function(t,e){for(var n,r=e,i=r*r,a=i*i*i,o=0;o<12&&(a=(i=(r-=n=(r*(Wf+Vf*i+a*(Gf+Xf*i))-e)/(Wf+3*Vf*i+a*(7*Gf+9*Xf*i)))*r)*i*i,!(xc(n)dc&&--i>0);return[t/(.8707+(a=r*r)*(a*(a*a*a*(.003971-.001529*a)-.013791)-.131979)),r]},id.invert=Df(Lc),od.invert=Df((function(t){return 2*wc(t)})),cd.invert=function(t,e){return[-e,2*wc(Cc(t))-gc]},bd.prototype=yd.prototype={constructor:bd,count:function(){return this.eachAfter(pd)},each:function(t){var e,n,r,i,a=this,o=[a];do{for(e=o.reverse(),o=[];a=e.pop();)if(t(a),n=a.children)for(r=0,i=n.length;r=0;--n)i.push(e[n]);return this},sum:function(t){return this.eachAfter((function(e){for(var n=+t(e.data)||0,r=e.children,i=r&&r.length;--i>=0;)n+=r[i].value;e.value=n}))},sort:function(t){return this.eachBefore((function(e){e.children&&e.children.sort(t)}))},path:function(t){for(var e=this,n=function(t,e){if(t===e)return t;var n=t.ancestors(),r=e.ancestors(),i=null;for(t=n.pop(),e=r.pop();t===e;)i=t,t=n.pop(),e=r.pop();return i}(e,t),r=[e];e!==n;)e=e.parent,r.push(e);for(var i=r.length;t!==n;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,e=[t];t=t.parent;)e.push(t);return e},descendants:function(){var t=[];return this.each((function(e){t.push(e)})),t},leaves:function(){var t=[];return this.eachBefore((function(e){e.children||t.push(e)})),t},links:function(){var t=this,e=[];return t.each((function(n){n!==t&&e.push({source:n.parent,target:n})})),e},copy:function(){return yd(this).eachBefore(md)}};var _d=Array.prototype.slice;function xd(t){for(var e,n,r=0,i=(t=function(t){for(var e,n,r=t.length;r;)n=Math.random()*r--|0,e=t[r],t[r]=t[n],t[n]=e;return t}(_d.call(t))).length,a=[];r0&&n*n>r*r+i*i}function Ed(t,e){for(var n=0;n(o*=o)?(r=(u+o-i)/(2*u),a=Math.sqrt(Math.max(0,o/u-r*r)),n.x=t.x-r*s-a*c,n.y=t.y-r*c+a*s):(r=(u+i-o)/(2*u),a=Math.sqrt(Math.max(0,i/u-r*r)),n.x=e.x+r*s-a*c,n.y=e.y+r*c+a*s)):(n.x=e.x+n.r,n.y=e.y)}function Nd(t,e){var n=t.r+e.r-1e-6,r=e.x-t.x,i=e.y-t.y;return n>0&&n*n>r*r+i*i}function Dd(t){var e=t._,n=t.next._,r=e.r+n.r,i=(e.x*n.r+n.x*e.r)/r,a=(e.y*n.r+n.y*e.r)/r;return i*i+a*a}function Od(t){this._=t,this.next=null,this.previous=null}function Bd(t){if(!(i=t.length))return 0;var e,n,r,i,a,o,s,c,u,l,h;if((e=t[0]).x=0,e.y=0,!(i>1))return e.r;if(n=t[1],e.x=-n.r,n.x=e.r,n.y=0,!(i>2))return e.r+n.r;Md(n,e,r=t[2]),e=new Od(e),n=new Od(n),r=new Od(r),e.next=r.previous=n,n.next=e.previous=r,r.next=n.previous=e;t:for(s=3;s0)throw new Error("cycle");return a}return n.id=function(e){return arguments.length?(t=Id(e),n):t},n.parentId=function(t){return arguments.length?(e=Id(t),n):e},n}function Kd(t,e){return t.parent===e.parent?1:2}function Qd(t){var e=t.children;return e?e[0]:t.t}function Jd(t){var e=t.children;return e?e[e.length-1]:t.t}function tp(t,e,n){var r=n/(e.i-t.i);e.c-=r,e.s+=n,t.c+=r,e.z+=n,e.m+=n}function ep(t,e,n){return t.a.parent===e.parent?t.a:n}function np(t,e){this._=t,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=e}function rp(){var t=Kd,e=1,n=1,r=null;function i(i){var c=function(t){for(var e,n,r,i,a,o=new np(t,0),s=[o];e=s.pop();)if(r=e._.children)for(e.children=new Array(a=r.length),i=a-1;i>=0;--i)s.push(n=e.children[i]=new np(r[i],i)),n.parent=e;return(o.parent=new np(null,0)).children=[o],o}(i);if(c.eachAfter(a),c.parent.m=-c.z,c.eachBefore(o),r)i.eachBefore(s);else{var u=i,l=i,h=i;i.eachBefore((function(t){t.xl.x&&(l=t),t.depth>h.depth&&(h=t)}));var f=u===l?1:t(u,l)/2,d=f-u.x,p=e/(l.x+f+d),y=n/(h.depth||1);i.eachBefore((function(t){t.x=(t.x+d)*p,t.y=t.depth*y}))}return i}function a(e){var n=e.children,r=e.parent.children,i=e.i?r[e.i-1]:null;if(n){!function(t){for(var e,n=0,r=0,i=t.children,a=i.length;--a>=0;)(e=i[a]).z+=n,e.m+=n,n+=e.s+(r+=e.c)}(e);var a=(n[0].z+n[n.length-1].z)/2;i?(e.z=i.z+t(e._,i._),e.m=e.z-a):e.z=a}else i&&(e.z=i.z+t(e._,i._));e.parent.A=function(e,n,r){if(n){for(var i,a=e,o=e,s=n,c=a.parent.children[0],u=a.m,l=o.m,h=s.m,f=c.m;s=Jd(s),a=Qd(a),s&&a;)c=Qd(c),(o=Jd(o)).a=e,(i=s.z+h-a.z-u+t(s._,a._))>0&&(tp(ep(s,e,r),e,i),u+=i,l+=i),h+=s.m,u+=a.m,f+=c.m,l+=o.m;s&&!Jd(o)&&(o.t=s,o.m+=h-l),a&&!Qd(c)&&(c.t=a,c.m+=u-f,r=e)}return r}(e,i,e.parent.A||r[0])}function o(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function s(t){t.x*=e,t.y=t.depth*n}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i}function ip(t,e,n,r,i){for(var a,o=t.children,s=-1,c=o.length,u=t.value&&(i-n)/t.value;++sf&&(f=s),g=l*l*y,(d=Math.max(f/g,g/h))>p){l-=s;break}p=d}m.push(o={value:l,dice:c1?e:1)},n}(ap);function cp(){var t=sp,e=!1,n=1,r=1,i=[0],a=Rd,o=Rd,s=Rd,c=Rd,u=Rd;function l(t){return t.x0=t.y0=0,t.x1=n,t.y1=r,t.eachBefore(h),i=[0],e&&t.eachBefore(qd),t}function h(e){var n=i[e.depth],r=e.x0+n,l=e.y0+n,h=e.x1-n,f=e.y1-n;h=n-1){var l=s[e];return l.x0=i,l.y0=a,l.x1=o,void(l.y1=c)}for(var h=u[e],f=r/2+h,d=e+1,p=n-1;d>>1;u[y]c-a){var v=(i*m+o*g)/r;t(e,d,g,i,a,v,c),t(d,n,m,v,a,o,c)}else{var b=(a*m+c*g)/r;t(e,d,g,i,a,o,b),t(d,n,m,i,b,o,c)}}(0,c,t.value,e,n,r,i)}function lp(t,e,n,r,i){(1&t.depth?ip:Hd)(t,e,n,r,i)}const hp=function t(e){function n(t,n,r,i,a){if((o=t._squarify)&&o.ratio===e)for(var o,s,c,u,l,h=-1,f=o.length,d=t.value;++h1?e:1)},n}(ap);function fp(t){var e=t.length;return function(n){return t[Math.max(0,Math.min(e-1,Math.floor(n*e)))]}}function dp(t,e){var n=hn(+t,+e);return function(t){var e=n(t);return e-360*Math.floor(e/360)}}function pp(t,e){return t=+t,e=+e,function(n){return Math.round(t*(1-n)+e*n)}}var yp=Math.SQRT2;function gp(t){return((t=Math.exp(t))+1/t)/2}function mp(t,e){var n,r,i=t[0],a=t[1],o=t[2],s=e[0],c=e[1],u=e[2],l=s-i,h=c-a,f=l*l+h*h;if(f<1e-12)r=Math.log(u/o)/yp,n=function(t){return[i+t*l,a+t*h,o*Math.exp(yp*t*r)]};else{var d=Math.sqrt(f),p=(u*u-o*o+4*f)/(2*o*2*d),y=(u*u-o*o-4*f)/(2*u*2*d),g=Math.log(Math.sqrt(p*p+1)-p),m=Math.log(Math.sqrt(y*y+1)-y);r=(m-g)/yp,n=function(t){var e,n=t*r,s=gp(g),c=o/(2*d)*(s*(e=yp*n+g,((e=Math.exp(2*e))-1)/(e+1))-function(t){return((t=Math.exp(t))-1/t)/2}(g));return[i+c*l,a+c*h,o*s/gp(yp*n+g)]}}return n.duration=1e3*r,n}function vp(t){return function(e,n){var r=t((e=nn(e)).h,(n=nn(n)).h),i=fn(e.s,n.s),a=fn(e.l,n.l),o=fn(e.opacity,n.opacity);return function(t){return e.h=r(t),e.s=i(t),e.l=a(t),e.opacity=o(t),e+""}}}const bp=vp(hn);var _p=vp(fn);function xp(t,e){var n=fn((t=ha(t)).l,(e=ha(e)).l),r=fn(t.a,e.a),i=fn(t.b,e.b),a=fn(t.opacity,e.opacity);return function(e){return t.l=n(e),t.a=r(e),t.b=i(e),t.opacity=a(e),t+""}}function wp(t){return function(e,n){var r=t((e=ba(e)).h,(n=ba(n)).h),i=fn(e.c,n.c),a=fn(e.l,n.l),o=fn(e.opacity,n.opacity);return function(t){return e.h=r(t),e.c=i(t),e.l=a(t),e.opacity=o(t),e+""}}}const kp=wp(hn);var Tp=wp(fn);function Ep(t){return function e(n){function r(e,r){var i=t((e=Na(e)).h,(r=Na(r)).h),a=fn(e.s,r.s),o=fn(e.l,r.l),s=fn(e.opacity,r.opacity);return function(t){return e.h=i(t),e.s=a(t),e.l=o(Math.pow(t,n)),e.opacity=s(t),e+""}}return n=+n,r.gamma=e,r}(1)}const Cp=Ep(hn);var Sp=Ep(fn);function Ap(t,e){for(var n=0,r=e.length-1,i=e[0],a=new Array(r<0?0:r);n1&&Op(t[n[r-2]],t[n[r-1]],t[i])<=0;)--r;n[r++]=i}return n.slice(0,r)}function Ip(t){if((n=t.length)<3)return null;var e,n,r=new Array(n),i=new Array(n);for(e=0;e=0;--e)u.push(t[r[a[e]][2]]);for(e=+s;es!=u>s&&o<(c-n)*(s-r)/(u-r)+n&&(l=!l),c=n,u=r;return l}function Fp(t){for(var e,n,r=-1,i=t.length,a=t[i-1],o=a[0],s=a[1],c=0;++r1);return t+n*a*Math.sqrt(-2*Math.log(i)/i)}}return n.source=t,n}(Pp),zp=function t(e){function n(){var t=Yp.source(e).apply(this,arguments);return function(){return Math.exp(t())}}return n.source=t,n}(Pp),Up=function t(e){function n(t){return function(){for(var n=0,r=0;rr&&(e=n,n=r,r=e),function(t){return Math.max(n,Math.min(r,t))}}function oy(t,e,n){var r=t[0],i=t[1],a=e[0],o=e[1];return i2?sy:oy,i=a=null,h}function h(e){return isNaN(e=+e)?n:(i||(i=r(o.map(t),s,c)))(t(u(e)))}return h.invert=function(n){return u(e((a||(a=r(s,o.map(t),wn)))(n)))},h.domain=function(t){return arguments.length?(o=Gp.call(t,ey),u===ry||(u=ay(o)),l()):o.slice()},h.range=function(t){return arguments.length?(s=Xp.call(t),l()):s.slice()},h.rangeRound=function(t){return s=Xp.call(t),c=pp,l()},h.clamp=function(t){return arguments.length?(u=t?ay(o):ry,h):u!==ry},h.interpolate=function(t){return arguments.length?(c=t,l()):c},h.unknown=function(t){return arguments.length?(n=t,h):n},function(n,r){return t=n,e=r,l()}}function ly(t,e){return uy()(t,e)}function hy(t,e,n,r){var i,a=M(t,e,n);switch((r=Gs(null==r?",f":r)).type){case"s":var o=Math.max(Math.abs(t),Math.abs(e));return null!=r.precision||isNaN(i=sc(a,o))||(r.precision=i),ec(r,o);case"":case"e":case"g":case"p":case"r":null!=r.precision||isNaN(i=cc(a,Math.max(Math.abs(t),Math.abs(e))))||(r.precision=i-("e"===r.type));break;case"f":case"%":null!=r.precision||isNaN(i=oc(a))||(r.precision=i-2*("%"===r.type))}return tc(r)}function fy(t){var e=t.domain;return t.ticks=function(t){var n=e();return S(n[0],n[n.length-1],null==t?10:t)},t.tickFormat=function(t,n){var r=e();return hy(r[0],r[r.length-1],null==t?10:t,n)},t.nice=function(n){null==n&&(n=10);var r,i=e(),a=0,o=i.length-1,s=i[a],c=i[o];return c0?r=A(s=Math.floor(s/r)*r,c=Math.ceil(c/r)*r,n):r<0&&(r=A(s=Math.ceil(s*r)/r,c=Math.floor(c*r)/r,n)),r>0?(i[a]=Math.floor(s/r)*r,i[o]=Math.ceil(c/r)*r,e(i)):r<0&&(i[a]=Math.ceil(s*r)/r,i[o]=Math.floor(c*r)/r,e(i)),t},t}function dy(){var t=ly(ry,ry);return t.copy=function(){return cy(t,dy())},$p.apply(t,arguments),fy(t)}function py(t){var e;function n(t){return isNaN(t=+t)?e:t}return n.invert=n,n.domain=n.range=function(e){return arguments.length?(t=Gp.call(e,ey),n):t.slice()},n.unknown=function(t){return arguments.length?(e=t,n):e},n.copy=function(){return py(t).unknown(e)},t=arguments.length?Gp.call(t,ey):[0,1],fy(n)}function yy(t,e){var n,r=0,i=(t=t.slice()).length-1,a=t[r],o=t[i];return o0){for(;fc)break;y.push(h)}}else for(;f=1;--l)if(!((h=u*l)c)break;y.push(h)}}else y=S(f,d,Math.min(d-f,p)).map(n);return r?y.reverse():y},r.tickFormat=function(t,i){if(null==i&&(i=10===a?".0e":","),"function"!=typeof i&&(i=tc(i)),t===1/0)return i;null==t&&(t=10);var o=Math.max(1,a*t/r.ticks().length);return function(t){var r=t/n(Math.round(e(t)));return r*a0?r[i-1]:e[0],i=r?[i[r-1],n]:[i[o-1],i[o]]},o.unknown=function(e){return arguments.length?(t=e,o):o},o.thresholds=function(){return i.slice()},o.copy=function(){return Iy().domain([e,n]).range(a).unknown(t)},$p.apply(fy(o),arguments)}function Ry(){var t,e=[.5],n=[0,1],r=1;function i(i){return i<=i?n[u(e,i,0,r)]:t}return i.domain=function(t){return arguments.length?(e=Xp.call(t),r=Math.min(e.length,n.length-1),i):e.slice()},i.range=function(t){return arguments.length?(n=Xp.call(t),r=Math.min(e.length,n.length-1),i):n.slice()},i.invertExtent=function(t){var r=n.indexOf(t);return[e[r-1],e[r]]},i.unknown=function(e){return arguments.length?(t=e,i):t},i.copy=function(){return Ry().domain(e).range(n).unknown(t)},$p.apply(i,arguments)}var Fy=new Date,Py=new Date;function jy(t,e,n,r){function i(e){return t(e=0===arguments.length?new Date:new Date(+e)),e}return i.floor=function(e){return t(e=new Date(+e)),e},i.ceil=function(n){return t(n=new Date(n-1)),e(n,1),t(n),n},i.round=function(t){var e=i(t),n=i.ceil(t);return t-e0))return s;do{s.push(o=new Date(+n)),e(n,a),t(n)}while(o=e)for(;t(e),!n(e);)e.setTime(e-1)}),(function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;e(t,-1),!n(t););else for(;--r>=0;)for(;e(t,1),!n(t););}))},n&&(i.count=function(e,r){return Fy.setTime(+e),Py.setTime(+r),t(Fy),t(Py),Math.floor(n(Fy,Py))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(e){return r(e)%t==0}:function(e){return i.count(0,e)%t==0}):i:null}),i}var Yy=jy((function(t){t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,e){t.setFullYear(t.getFullYear()+e)}),(function(t,e){return e.getFullYear()-t.getFullYear()}),(function(t){return t.getFullYear()}));Yy.every=function(t){return isFinite(t=Math.floor(t))&&t>0?jy((function(e){e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)}),(function(e,n){e.setFullYear(e.getFullYear()+n*t)})):null};const zy=Yy;var Uy=Yy.range,qy=jy((function(t){t.setDate(1),t.setHours(0,0,0,0)}),(function(t,e){t.setMonth(t.getMonth()+e)}),(function(t,e){return e.getMonth()-t.getMonth()+12*(e.getFullYear()-t.getFullYear())}),(function(t){return t.getMonth()}));const Hy=qy;var $y=qy.range,Wy=1e3,Vy=6e4,Gy=36e5,Xy=864e5,Zy=6048e5;function Ky(t){return jy((function(e){e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+7*e)}),(function(t,e){return(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*Vy)/Zy}))}var Qy=Ky(0),Jy=Ky(1),tg=Ky(2),eg=Ky(3),ng=Ky(4),rg=Ky(5),ig=Ky(6),ag=Qy.range,og=Jy.range,sg=tg.range,cg=eg.range,ug=ng.range,lg=rg.range,hg=ig.range,fg=jy((function(t){t.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+e)}),(function(t,e){return(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*Vy)/Xy}),(function(t){return t.getDate()-1}));const dg=fg;var pg=fg.range,yg=jy((function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*Wy-t.getMinutes()*Vy)}),(function(t,e){t.setTime(+t+e*Gy)}),(function(t,e){return(e-t)/Gy}),(function(t){return t.getHours()}));const gg=yg;var mg=yg.range,vg=jy((function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*Wy)}),(function(t,e){t.setTime(+t+e*Vy)}),(function(t,e){return(e-t)/Vy}),(function(t){return t.getMinutes()}));const bg=vg;var _g=vg.range,xg=jy((function(t){t.setTime(t-t.getMilliseconds())}),(function(t,e){t.setTime(+t+e*Wy)}),(function(t,e){return(e-t)/Wy}),(function(t){return t.getUTCSeconds()}));const wg=xg;var kg=xg.range,Tg=jy((function(){}),(function(t,e){t.setTime(+t+e)}),(function(t,e){return e-t}));Tg.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?jy((function(e){e.setTime(Math.floor(e/t)*t)}),(function(e,n){e.setTime(+e+n*t)}),(function(e,n){return(n-e)/t})):Tg:null};const Eg=Tg;var Cg=Tg.range;function Sg(t){return jy((function(e){e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+7*e)}),(function(t,e){return(e-t)/Zy}))}var Ag=Sg(0),Mg=Sg(1),Ng=Sg(2),Dg=Sg(3),Og=Sg(4),Bg=Sg(5),Lg=Sg(6),Ig=Ag.range,Rg=Mg.range,Fg=Ng.range,Pg=Dg.range,jg=Og.range,Yg=Bg.range,zg=Lg.range,Ug=jy((function(t){t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+e)}),(function(t,e){return(e-t)/Xy}),(function(t){return t.getUTCDate()-1}));const qg=Ug;var Hg=Ug.range,$g=jy((function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCFullYear(t.getUTCFullYear()+e)}),(function(t,e){return e.getUTCFullYear()-t.getUTCFullYear()}),(function(t){return t.getUTCFullYear()}));$g.every=function(t){return isFinite(t=Math.floor(t))&&t>0?jy((function(e){e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)}),(function(e,n){e.setUTCFullYear(e.getUTCFullYear()+n*t)})):null};const Wg=$g;var Vg=$g.range;function Gg(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function Xg(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Zg(t,e,n){return{y:t,m:e,d:n,H:0,M:0,S:0,L:0}}function Kg(t){var e=t.dateTime,n=t.date,r=t.time,i=t.periods,a=t.days,o=t.shortDays,s=t.months,c=t.shortMonths,u=um(i),l=lm(i),h=um(a),f=lm(a),d=um(o),p=lm(o),y=um(s),g=lm(s),m=um(c),v=lm(c),b={a:function(t){return o[t.getDay()]},A:function(t){return a[t.getDay()]},b:function(t){return c[t.getMonth()]},B:function(t){return s[t.getMonth()]},c:null,d:Dm,e:Dm,f:Rm,g:Vm,G:Xm,H:Om,I:Bm,j:Lm,L:Im,m:Fm,M:Pm,p:function(t){return i[+(t.getHours()>=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:vv,s:bv,S:jm,u:Ym,U:zm,V:qm,w:Hm,W:$m,x:null,X:null,y:Wm,Y:Gm,Z:Zm,"%":mv},_={a:function(t){return o[t.getUTCDay()]},A:function(t){return a[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return s[t.getUTCMonth()]},c:null,d:Km,e:Km,f:nv,g:dv,G:yv,H:Qm,I:Jm,j:tv,L:ev,m:rv,M:iv,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:vv,s:bv,S:av,u:ov,U:sv,V:uv,w:lv,W:hv,x:null,X:null,y:fv,Y:pv,Z:gv,"%":mv},x={a:function(t,e,n){var r=d.exec(e.slice(n));return r?(t.w=p[r[0].toLowerCase()],n+r[0].length):-1},A:function(t,e,n){var r=h.exec(e.slice(n));return r?(t.w=f[r[0].toLowerCase()],n+r[0].length):-1},b:function(t,e,n){var r=m.exec(e.slice(n));return r?(t.m=v[r[0].toLowerCase()],n+r[0].length):-1},B:function(t,e,n){var r=y.exec(e.slice(n));return r?(t.m=g[r[0].toLowerCase()],n+r[0].length):-1},c:function(t,n,r){return T(t,e,n,r)},d:xm,e:xm,f:Sm,g:mm,G:gm,H:km,I:km,j:wm,L:Cm,m:_m,M:Tm,p:function(t,e,n){var r=u.exec(e.slice(n));return r?(t.p=l[r[0].toLowerCase()],n+r[0].length):-1},q:bm,Q:Mm,s:Nm,S:Em,u:fm,U:dm,V:pm,w:hm,W:ym,x:function(t,e,r){return T(t,n,e,r)},X:function(t,e,n){return T(t,r,e,n)},y:mm,Y:gm,Z:vm,"%":Am};function w(t,e){return function(n){var r,i,a,o=[],s=-1,c=0,u=t.length;for(n instanceof Date||(n=new Date(+n));++s53)return null;"w"in a||(a.w=1),"Z"in a?(i=(r=Xg(Zg(a.y,0,1))).getUTCDay(),r=i>4||0===i?Mg.ceil(r):Mg(r),r=qg.offset(r,7*(a.V-1)),a.y=r.getUTCFullYear(),a.m=r.getUTCMonth(),a.d=r.getUTCDate()+(a.w+6)%7):(i=(r=Gg(Zg(a.y,0,1))).getDay(),r=i>4||0===i?Jy.ceil(r):Jy(r),r=dg.offset(r,7*(a.V-1)),a.y=r.getFullYear(),a.m=r.getMonth(),a.d=r.getDate()+(a.w+6)%7)}else("W"in a||"U"in a)&&("w"in a||(a.w="u"in a?a.u%7:"W"in a?1:0),i="Z"in a?Xg(Zg(a.y,0,1)).getUTCDay():Gg(Zg(a.y,0,1)).getDay(),a.m=0,a.d="W"in a?(a.w+6)%7+7*a.W-(i+5)%7:a.w+7*a.U-(i+6)%7);return"Z"in a?(a.H+=a.Z/100|0,a.M+=a.Z%100,Xg(a)):Gg(a)}}function T(t,e,n,r){for(var i,a,o=0,s=e.length,c=n.length;o=c)return-1;if(37===(i=e.charCodeAt(o++))){if(i=e.charAt(o++),!(a=x[i in rm?e.charAt(o++):i])||(r=a(t,n,r))<0)return-1}else if(i!=n.charCodeAt(r++))return-1}return r}return b.x=w(n,b),b.X=w(r,b),b.c=w(e,b),_.x=w(n,_),_.X=w(r,_),_.c=w(e,_),{format:function(t){var e=w(t+="",b);return e.toString=function(){return t},e},parse:function(t){var e=k(t+="",!1);return e.toString=function(){return t},e},utcFormat:function(t){var e=w(t+="",_);return e.toString=function(){return t},e},utcParse:function(t){var e=k(t+="",!0);return e.toString=function(){return t},e}}}var Qg,Jg,tm,em,nm,rm={"-":"",_:" ",0:"0"},im=/^\s*\d+/,am=/^%/,om=/[\\^$*+?|[\]().{}]/g;function sm(t,e,n){var r=t<0?"-":"",i=(r?-t:t)+"",a=i.length;return r+(a68?1900:2e3),n+r[0].length):-1}function vm(t,e,n){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(n,n+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),n+r[0].length):-1}function bm(t,e,n){var r=im.exec(e.slice(n,n+1));return r?(t.q=3*r[0]-3,n+r[0].length):-1}function _m(t,e,n){var r=im.exec(e.slice(n,n+2));return r?(t.m=r[0]-1,n+r[0].length):-1}function xm(t,e,n){var r=im.exec(e.slice(n,n+2));return r?(t.d=+r[0],n+r[0].length):-1}function wm(t,e,n){var r=im.exec(e.slice(n,n+3));return r?(t.m=0,t.d=+r[0],n+r[0].length):-1}function km(t,e,n){var r=im.exec(e.slice(n,n+2));return r?(t.H=+r[0],n+r[0].length):-1}function Tm(t,e,n){var r=im.exec(e.slice(n,n+2));return r?(t.M=+r[0],n+r[0].length):-1}function Em(t,e,n){var r=im.exec(e.slice(n,n+2));return r?(t.S=+r[0],n+r[0].length):-1}function Cm(t,e,n){var r=im.exec(e.slice(n,n+3));return r?(t.L=+r[0],n+r[0].length):-1}function Sm(t,e,n){var r=im.exec(e.slice(n,n+6));return r?(t.L=Math.floor(r[0]/1e3),n+r[0].length):-1}function Am(t,e,n){var r=am.exec(e.slice(n,n+1));return r?n+r[0].length:-1}function Mm(t,e,n){var r=im.exec(e.slice(n));return r?(t.Q=+r[0],n+r[0].length):-1}function Nm(t,e,n){var r=im.exec(e.slice(n));return r?(t.s=+r[0],n+r[0].length):-1}function Dm(t,e){return sm(t.getDate(),e,2)}function Om(t,e){return sm(t.getHours(),e,2)}function Bm(t,e){return sm(t.getHours()%12||12,e,2)}function Lm(t,e){return sm(1+dg.count(zy(t),t),e,3)}function Im(t,e){return sm(t.getMilliseconds(),e,3)}function Rm(t,e){return Im(t,e)+"000"}function Fm(t,e){return sm(t.getMonth()+1,e,2)}function Pm(t,e){return sm(t.getMinutes(),e,2)}function jm(t,e){return sm(t.getSeconds(),e,2)}function Ym(t){var e=t.getDay();return 0===e?7:e}function zm(t,e){return sm(Qy.count(zy(t)-1,t),e,2)}function Um(t){var e=t.getDay();return e>=4||0===e?ng(t):ng.ceil(t)}function qm(t,e){return t=Um(t),sm(ng.count(zy(t),t)+(4===zy(t).getDay()),e,2)}function Hm(t){return t.getDay()}function $m(t,e){return sm(Jy.count(zy(t)-1,t),e,2)}function Wm(t,e){return sm(t.getFullYear()%100,e,2)}function Vm(t,e){return sm((t=Um(t)).getFullYear()%100,e,2)}function Gm(t,e){return sm(t.getFullYear()%1e4,e,4)}function Xm(t,e){var n=t.getDay();return sm((t=n>=4||0===n?ng(t):ng.ceil(t)).getFullYear()%1e4,e,4)}function Zm(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+sm(e/60|0,"0",2)+sm(e%60,"0",2)}function Km(t,e){return sm(t.getUTCDate(),e,2)}function Qm(t,e){return sm(t.getUTCHours(),e,2)}function Jm(t,e){return sm(t.getUTCHours()%12||12,e,2)}function tv(t,e){return sm(1+qg.count(Wg(t),t),e,3)}function ev(t,e){return sm(t.getUTCMilliseconds(),e,3)}function nv(t,e){return ev(t,e)+"000"}function rv(t,e){return sm(t.getUTCMonth()+1,e,2)}function iv(t,e){return sm(t.getUTCMinutes(),e,2)}function av(t,e){return sm(t.getUTCSeconds(),e,2)}function ov(t){var e=t.getUTCDay();return 0===e?7:e}function sv(t,e){return sm(Ag.count(Wg(t)-1,t),e,2)}function cv(t){var e=t.getUTCDay();return e>=4||0===e?Og(t):Og.ceil(t)}function uv(t,e){return t=cv(t),sm(Og.count(Wg(t),t)+(4===Wg(t).getUTCDay()),e,2)}function lv(t){return t.getUTCDay()}function hv(t,e){return sm(Mg.count(Wg(t)-1,t),e,2)}function fv(t,e){return sm(t.getUTCFullYear()%100,e,2)}function dv(t,e){return sm((t=cv(t)).getUTCFullYear()%100,e,2)}function pv(t,e){return sm(t.getUTCFullYear()%1e4,e,4)}function yv(t,e){var n=t.getUTCDay();return sm((t=n>=4||0===n?Og(t):Og.ceil(t)).getUTCFullYear()%1e4,e,4)}function gv(){return"+0000"}function mv(){return"%"}function vv(t){return+t}function bv(t){return Math.floor(+t/1e3)}function _v(t){return Qg=Kg(t),Jg=Qg.format,tm=Qg.parse,em=Qg.utcFormat,nm=Qg.utcParse,Qg}_v({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var xv=31536e6;function wv(t){return new Date(t)}function kv(t){return t instanceof Date?+t:+new Date(+t)}function Tv(t,e,n,r,i,o,s,c,u){var l=ly(ry,ry),h=l.invert,f=l.domain,d=u(".%L"),p=u(":%S"),y=u("%I:%M"),g=u("%I %p"),m=u("%a %d"),v=u("%b %d"),b=u("%B"),_=u("%Y"),x=[[s,1,1e3],[s,5,5e3],[s,15,15e3],[s,30,3e4],[o,1,6e4],[o,5,3e5],[o,15,9e5],[o,30,18e5],[i,1,36e5],[i,3,108e5],[i,6,216e5],[i,12,432e5],[r,1,864e5],[r,2,1728e5],[n,1,6048e5],[e,1,2592e6],[e,3,7776e6],[t,1,xv]];function w(a){return(s(a)1)&&(t-=Math.floor(t));var e=Math.abs(t-.5);return f_.h=360*t-100,f_.s=1.5-1.5*e,f_.l=.8-.9*e,f_+""}var p_=Xe(),y_=Math.PI/3,g_=2*Math.PI/3;function m_(t){var e;return t=(.5-t)*Math.PI,p_.r=255*(e=Math.sin(t))*e,p_.g=255*(e=Math.sin(t+y_))*e,p_.b=255*(e=Math.sin(t+g_))*e,p_+""}function v_(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-14825.05*t)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+707.56*t)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-6838.66*t)))))))+")"}function b_(t){var e=t.length;return function(n){return t[Math.max(0,Math.min(e-1,Math.floor(n*e)))]}}const __=b_(Zv("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725"));var x_=b_(Zv("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),w_=b_(Zv("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),k_=b_(Zv("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921"));function T_(t){return we(re(t).call(document.documentElement))}var E_=0;function C_(){return new S_}function S_(){this._="@"+(++E_).toString(36)}function A_(t){return"string"==typeof t?new be([document.querySelectorAll(t)],[document.documentElement]):new be([null==t?[]:t],ve)}function M_(t,e){null==e&&(e=An().touches);for(var n=0,r=e?e.length:0,i=new Array(r);n=1?Y_:t<=-1?-Y_:Math.asin(t)}function q_(t){return t.innerRadius}function H_(t){return t.outerRadius}function $_(t){return t.startAngle}function W_(t){return t.endAngle}function V_(t){return t&&t.padAngle}function G_(t,e,n,r,i,a,o){var s=t-n,c=e-r,u=(o?a:-a)/F_(s*s+c*c),l=u*c,h=-u*s,f=t+l,d=e+h,p=n+l,y=r+h,g=(f+p)/2,m=(d+y)/2,v=p-f,b=y-d,_=v*v+b*b,x=i-a,w=f*y-p*d,k=(b<0?-1:1)*F_(L_(0,x*x*_-w*w)),T=(w*b-v*k)/_,E=(-w*v-b*k)/_,C=(w*b+v*k)/_,S=(-w*v+b*k)/_,A=T-g,M=E-m,N=C-g,D=S-m;return A*A+M*M>N*N+D*D&&(T=C,E=S),{cx:T,cy:E,x01:-l,y01:-h,x11:T*(i/x-1),y11:E*(i/x-1)}}function X_(){var t=q_,e=H_,n=N_(0),r=null,i=$_,a=W_,o=V_,s=null;function c(){var c,u,l=+t.apply(this,arguments),h=+e.apply(this,arguments),f=i.apply(this,arguments)-Y_,d=a.apply(this,arguments)-Y_,p=D_(d-f),y=d>f;if(s||(s=c=Bi()),hP_)if(p>z_-P_)s.moveTo(h*B_(f),h*R_(f)),s.arc(0,0,h,f,d,!y),l>P_&&(s.moveTo(l*B_(d),l*R_(d)),s.arc(0,0,l,d,f,y));else{var g,m,v=f,b=d,_=f,x=d,w=p,k=p,T=o.apply(this,arguments)/2,E=T>P_&&(r?+r.apply(this,arguments):F_(l*l+h*h)),C=I_(D_(h-l)/2,+n.apply(this,arguments)),S=C,A=C;if(E>P_){var M=U_(E/l*R_(T)),N=U_(E/h*R_(T));(w-=2*M)>P_?(_+=M*=y?1:-1,x-=M):(w=0,_=x=(f+d)/2),(k-=2*N)>P_?(v+=N*=y?1:-1,b-=N):(k=0,v=b=(f+d)/2)}var D=h*B_(v),O=h*R_(v),B=l*B_(x),L=l*R_(x);if(C>P_){var I,R=h*B_(b),F=h*R_(b),P=l*B_(_),j=l*R_(_);if(p1?0:t<-1?j_:Math.acos(t)}((Y*U+z*q)/(F_(Y*Y+z*z)*F_(U*U+q*q)))/2),$=F_(I[0]*I[0]+I[1]*I[1]);S=I_(C,(l-$)/(H-1)),A=I_(C,(h-$)/(H+1))}}k>P_?A>P_?(g=G_(P,j,D,O,h,A,y),m=G_(R,F,B,L,h,A,y),s.moveTo(g.cx+g.x01,g.cy+g.y01),AP_&&w>P_?S>P_?(g=G_(B,L,R,F,l,-S,y),m=G_(D,O,P,j,l,-S,y),s.lineTo(g.cx+g.x01,g.cy+g.y01),S=l;--h)s.point(g[h],m[h]);s.lineEnd(),s.areaEnd()}y&&(g[u]=+t(f,u,c),m[u]=+n(f,u,c),s.point(e?+e(f,u,c):g[u],r?+r(f,u,c):m[u]))}if(d)return s=null,d+""||null}function u(){return tx().defined(i).curve(o).context(a)}return c.x=function(n){return arguments.length?(t="function"==typeof n?n:N_(+n),e=null,c):t},c.x0=function(e){return arguments.length?(t="function"==typeof e?e:N_(+e),c):t},c.x1=function(t){return arguments.length?(e=null==t?null:"function"==typeof t?t:N_(+t),c):e},c.y=function(t){return arguments.length?(n="function"==typeof t?t:N_(+t),r=null,c):n},c.y0=function(t){return arguments.length?(n="function"==typeof t?t:N_(+t),c):n},c.y1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:N_(+t),c):r},c.lineX0=c.lineY0=function(){return u().x(t).y(n)},c.lineY1=function(){return u().x(t).y(r)},c.lineX1=function(){return u().x(e).y(n)},c.defined=function(t){return arguments.length?(i="function"==typeof t?t:N_(!!t),c):i},c.curve=function(t){return arguments.length?(o=t,null!=a&&(s=o(a)),c):o},c.context=function(t){return arguments.length?(null==t?a=s=null:s=o(a=t),c):a},c}function nx(t,e){return et?1:e>=t?0:NaN}function rx(t){return t}function ix(){var t=rx,e=nx,n=null,r=N_(0),i=N_(z_),a=N_(0);function o(o){var s,c,u,l,h,f=o.length,d=0,p=new Array(f),y=new Array(f),g=+r.apply(this,arguments),m=Math.min(z_,Math.max(-z_,i.apply(this,arguments)-g)),v=Math.min(Math.abs(m)/f,a.apply(this,arguments)),b=v*(m<0?-1:1);for(s=0;s0&&(d+=h);for(null!=e?p.sort((function(t,n){return e(y[t],y[n])})):null!=n&&p.sort((function(t,e){return n(o[t],o[e])})),s=0,u=d?(m-f*b)/d:0;s0?h*u:0)+b,y[c]={data:o[c],index:s,value:h,startAngle:g,endAngle:l,padAngle:v};return y}return o.value=function(e){return arguments.length?(t="function"==typeof e?e:N_(+e),o):t},o.sortValues=function(t){return arguments.length?(e=t,n=null,o):e},o.sort=function(t){return arguments.length?(n=t,e=null,o):n},o.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:N_(+t),o):r},o.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:N_(+t),o):i},o.padAngle=function(t){return arguments.length?(a="function"==typeof t?t:N_(+t),o):a},o}Z_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:this._context.lineTo(t,e)}}};var ax=sx(K_);function ox(t){this._curve=t}function sx(t){function e(e){return new ox(t(e))}return e._curve=t,e}function cx(t){var e=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?e(sx(t)):e()._curve},t}function ux(){return cx(tx().curve(ax))}function lx(){var t=ex().curve(ax),e=t.curve,n=t.lineX0,r=t.lineX1,i=t.lineY0,a=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return cx(n())},delete t.lineX0,t.lineEndAngle=function(){return cx(r())},delete t.lineX1,t.lineInnerRadius=function(){return cx(i())},delete t.lineY0,t.lineOuterRadius=function(){return cx(a())},delete t.lineY1,t.curve=function(t){return arguments.length?e(sx(t)):e()._curve},t}function hx(t,e){return[(e=+e)*Math.cos(t-=Math.PI/2),e*Math.sin(t)]}ox.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,e){this._curve.point(e*Math.sin(t),e*-Math.cos(t))}};var fx=Array.prototype.slice;function dx(t){return t.source}function px(t){return t.target}function yx(t){var e=dx,n=px,r=Q_,i=J_,a=null;function o(){var o,s=fx.call(arguments),c=e.apply(this,s),u=n.apply(this,s);if(a||(a=o=Bi()),t(a,+r.apply(this,(s[0]=c,s)),+i.apply(this,s),+r.apply(this,(s[0]=u,s)),+i.apply(this,s)),o)return a=null,o+""||null}return o.source=function(t){return arguments.length?(e=t,o):e},o.target=function(t){return arguments.length?(n=t,o):n},o.x=function(t){return arguments.length?(r="function"==typeof t?t:N_(+t),o):r},o.y=function(t){return arguments.length?(i="function"==typeof t?t:N_(+t),o):i},o.context=function(t){return arguments.length?(a=null==t?null:t,o):a},o}function gx(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e=(e+r)/2,n,e,i,r,i)}function mx(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e,n=(n+i)/2,r,n,r,i)}function vx(t,e,n,r,i){var a=hx(e,n),o=hx(e,n=(n+i)/2),s=hx(r,n),c=hx(r,i);t.moveTo(a[0],a[1]),t.bezierCurveTo(o[0],o[1],s[0],s[1],c[0],c[1])}function bx(){return yx(gx)}function _x(){return yx(mx)}function xx(){var t=yx(vx);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t}const wx={draw:function(t,e){var n=Math.sqrt(e/j_);t.moveTo(n,0),t.arc(0,0,n,0,z_)}},kx={draw:function(t,e){var n=Math.sqrt(e/5)/2;t.moveTo(-3*n,-n),t.lineTo(-n,-n),t.lineTo(-n,-3*n),t.lineTo(n,-3*n),t.lineTo(n,-n),t.lineTo(3*n,-n),t.lineTo(3*n,n),t.lineTo(n,n),t.lineTo(n,3*n),t.lineTo(-n,3*n),t.lineTo(-n,n),t.lineTo(-3*n,n),t.closePath()}};var Tx=Math.sqrt(1/3),Ex=2*Tx;const Cx={draw:function(t,e){var n=Math.sqrt(e/Ex),r=n*Tx;t.moveTo(0,-n),t.lineTo(r,0),t.lineTo(0,n),t.lineTo(-r,0),t.closePath()}};var Sx=Math.sin(j_/10)/Math.sin(7*j_/10),Ax=Math.sin(z_/10)*Sx,Mx=-Math.cos(z_/10)*Sx;const Nx={draw:function(t,e){var n=Math.sqrt(.8908130915292852*e),r=Ax*n,i=Mx*n;t.moveTo(0,-n),t.lineTo(r,i);for(var a=1;a<5;++a){var o=z_*a/5,s=Math.cos(o),c=Math.sin(o);t.lineTo(c*n,-s*n),t.lineTo(s*r-c*i,c*r+s*i)}t.closePath()}},Dx={draw:function(t,e){var n=Math.sqrt(e),r=-n/2;t.rect(r,r,n,n)}};var Ox=Math.sqrt(3);const Bx={draw:function(t,e){var n=-Math.sqrt(e/(3*Ox));t.moveTo(0,2*n),t.lineTo(-Ox*n,-n),t.lineTo(Ox*n,-n),t.closePath()}};var Lx=-.5,Ix=Math.sqrt(3)/2,Rx=1/Math.sqrt(12),Fx=3*(Rx/2+1);const Px={draw:function(t,e){var n=Math.sqrt(e/Fx),r=n/2,i=n*Rx,a=r,o=n*Rx+n,s=-a,c=o;t.moveTo(r,i),t.lineTo(a,o),t.lineTo(s,c),t.lineTo(Lx*r-Ix*i,Ix*r+Lx*i),t.lineTo(Lx*a-Ix*o,Ix*a+Lx*o),t.lineTo(Lx*s-Ix*c,Ix*s+Lx*c),t.lineTo(Lx*r+Ix*i,Lx*i-Ix*r),t.lineTo(Lx*a+Ix*o,Lx*o-Ix*a),t.lineTo(Lx*s+Ix*c,Lx*c-Ix*s),t.closePath()}};var jx=[wx,kx,Cx,Dx,Nx,Bx,Px];function Yx(){var t=N_(wx),e=N_(64),n=null;function r(){var r;if(n||(n=r=Bi()),t.apply(this,arguments).draw(n,+e.apply(this,arguments)),r)return n=null,r+""||null}return r.type=function(e){return arguments.length?(t="function"==typeof e?e:N_(e),r):t},r.size=function(t){return arguments.length?(e="function"==typeof t?t:N_(+t),r):e},r.context=function(t){return arguments.length?(n=null==t?null:t,r):n},r}function zx(){}function Ux(t,e,n){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+n)/6)}function qx(t){this._context=t}function Hx(t){return new qx(t)}function $x(t){this._context=t}function Wx(t){return new $x(t)}function Vx(t){this._context=t}function Gx(t){return new Vx(t)}function Xx(t,e){this._basis=new qx(t),this._beta=e}qx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:Ux(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:Ux(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}},$x.prototype={areaStart:zx,areaEnd:zx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:Ux(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}},Vx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:Ux(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}},Xx.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,e=this._y,n=t.length-1;if(n>0)for(var r,i=t[0],a=e[0],o=t[n]-i,s=e[n]-a,c=-1;++c<=n;)r=c/n,this._basis.point(this._beta*t[c]+(1-this._beta)*(i+r*o),this._beta*e[c]+(1-this._beta)*(a+r*s));this._x=this._y=null,this._basis.lineEnd()},point:function(t,e){this._x.push(+t),this._y.push(+e)}};const Zx=function t(e){function n(t){return 1===e?new qx(t):new Xx(t,e)}return n.beta=function(e){return t(+e)},n}(.85);function Kx(t,e,n){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-e),t._y2+t._k*(t._y1-n),t._x2,t._y2)}function Qx(t,e){this._context=t,this._k=(1-e)/6}Qx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:Kx(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2,this._x1=t,this._y1=e;break;case 2:this._point=3;default:Kx(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const Jx=function t(e){function n(t){return new Qx(t,e)}return n.tension=function(e){return t(+e)},n}(0);function tw(t,e){this._context=t,this._k=(1-e)/6}tw.prototype={areaStart:zx,areaEnd:zx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:Kx(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const ew=function t(e){function n(t){return new tw(t,e)}return n.tension=function(e){return t(+e)},n}(0);function nw(t,e){this._context=t,this._k=(1-e)/6}nw.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Kx(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const rw=function t(e){function n(t){return new nw(t,e)}return n.tension=function(e){return t(+e)},n}(0);function iw(t,e,n){var r=t._x1,i=t._y1,a=t._x2,o=t._y2;if(t._l01_a>P_){var s=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*s-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*s-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>P_){var u=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,l=3*t._l23_a*(t._l23_a+t._l12_a);a=(a*u+t._x1*t._l23_2a-e*t._l12_2a)/l,o=(o*u+t._y1*t._l23_2a-n*t._l12_2a)/l}t._context.bezierCurveTo(r,i,a,o,t._x2,t._y2)}function aw(t,e){this._context=t,this._alpha=e}aw.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3;default:iw(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const ow=function t(e){function n(t){return e?new aw(t,e):new Qx(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function sw(t,e){this._context=t,this._alpha=e}sw.prototype={areaStart:zx,areaEnd:zx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:iw(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const cw=function t(e){function n(t){return e?new sw(t,e):new tw(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function uw(t,e){this._context=t,this._alpha=e}uw.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:iw(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const lw=function t(e){function n(t){return e?new uw(t,e):new nw(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function hw(t){this._context=t}function fw(t){return new hw(t)}function dw(t){return t<0?-1:1}function pw(t,e,n){var r=t._x1-t._x0,i=e-t._x1,a=(t._y1-t._y0)/(r||i<0&&-0),o=(n-t._y1)/(i||r<0&&-0),s=(a*i+o*r)/(r+i);return(dw(a)+dw(o))*Math.min(Math.abs(a),Math.abs(o),.5*Math.abs(s))||0}function yw(t,e){var n=t._x1-t._x0;return n?(3*(t._y1-t._y0)/n-e)/2:e}function gw(t,e,n){var r=t._x0,i=t._y0,a=t._x1,o=t._y1,s=(a-r)/3;t._context.bezierCurveTo(r+s,i+s*e,a-s,o-s*n,a,o)}function mw(t){this._context=t}function vw(t){this._context=new bw(t)}function bw(t){this._context=t}function _w(t){return new mw(t)}function xw(t){return new vw(t)}function ww(t){this._context=t}function kw(t){var e,n,r=t.length-1,i=new Array(r),a=new Array(r),o=new Array(r);for(i[0]=0,a[0]=2,o[0]=t[0]+2*t[1],e=1;e=0;--e)i[e]=(o[e]-i[e+1])/a[e];for(a[r-1]=(t[r]+i[r-1])/2,e=0;e1)for(var n,r,i,a=1,o=t[e[0]],s=o.length;a=0;)n[e]=e;return n}function Dw(t,e){return t[e]}function Ow(){var t=N_([]),e=Nw,n=Mw,r=Dw;function i(i){var a,o,s=t.apply(this,arguments),c=i.length,u=s.length,l=new Array(u);for(a=0;a0){for(var n,r,i,a=0,o=t[0].length;a0)for(var n,r,i,a,o,s,c=0,u=t[e[0]].length;c0?(r[0]=a,r[1]=a+=i):i<0?(r[1]=o,r[0]=o+=i):(r[0]=0,r[1]=i)}function Iw(t,e){if((n=t.length)>0){for(var n,r=0,i=t[e[0]],a=i.length;r0&&(r=(n=t[e[0]]).length)>0){for(var n,r,i,a=0,o=1;oa&&(a=e,r=n);return r}function jw(t){var e=t.map(Yw);return Nw(t).sort((function(t,n){return e[t]-e[n]}))}function Yw(t){for(var e,n=0,r=-1,i=t.length;++r=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var n=this._x*(1-this._t)+t*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,e)}}this._x=t,this._y=e}};var Hw="%Y-%m-%dT%H:%M:%S.%LZ",$w=Date.prototype.toISOString?function(t){return t.toISOString()}:em(Hw);const Ww=$w;var Vw=+new Date("2000-01-01T00:00:00.000Z")?function(t){var e=new Date(t);return isNaN(e)?null:e}:nm(Hw);const Gw=Vw;function Xw(t,e,n){var r=new Hn,i=e;return null==e?(r.restart(t,e,n),r):(e=+e,n=null==n?Un():+n,r.restart((function a(o){o+=i,r.restart(a,i+=e,n),t(o)}),e,n),r)}function Zw(t){return function(){return t}}function Kw(t){return t[0]}function Qw(t){return t[1]}function Jw(){this._=null}function tk(t){t.U=t.C=t.L=t.R=t.P=t.N=null}function ek(t,e){var n=e,r=e.R,i=n.U;i?i.L===n?i.L=r:i.R=r:t._=r,r.U=i,n.U=r,n.R=r.L,n.R&&(n.R.U=n),r.L=n}function nk(t,e){var n=e,r=e.L,i=n.U;i?i.L===n?i.L=r:i.R=r:t._=r,r.U=i,n.U=r,n.L=r.R,n.L&&(n.L.U=n),r.R=n}function rk(t){for(;t.L;)t=t.L;return t}Jw.prototype={constructor:Jw,insert:function(t,e){var n,r,i;if(t){if(e.P=t,e.N=t.N,t.N&&(t.N.P=e),t.N=e,t.R){for(t=t.R;t.L;)t=t.L;t.L=e}else t.R=e;n=t}else this._?(t=rk(this._),e.P=null,e.N=t,t.P=t.L=e,n=t):(e.P=e.N=null,this._=e,n=null);for(e.L=e.R=null,e.U=n,e.C=!0,t=e;n&&n.C;)n===(r=n.U).L?(i=r.R)&&i.C?(n.C=i.C=!1,r.C=!0,t=r):(t===n.R&&(ek(this,n),n=(t=n).U),n.C=!1,r.C=!0,nk(this,r)):(i=r.L)&&i.C?(n.C=i.C=!1,r.C=!0,t=r):(t===n.L&&(nk(this,n),n=(t=n).U),n.C=!1,r.C=!0,ek(this,r)),n=t.U;this._.C=!1},remove:function(t){t.N&&(t.N.P=t.P),t.P&&(t.P.N=t.N),t.N=t.P=null;var e,n,r,i=t.U,a=t.L,o=t.R;if(n=a?o?rk(o):a:o,i?i.L===t?i.L=n:i.R=n:this._=n,a&&o?(r=n.C,n.C=t.C,n.L=a,a.U=n,n!==o?(i=n.U,n.U=t.U,t=n.R,i.L=t,n.R=o,o.U=n):(n.U=i,i=n,t=n.R)):(r=t.C,t=n),t&&(t.U=i),!r)if(t&&t.C)t.C=!1;else{do{if(t===this._)break;if(t===i.L){if((e=i.R).C&&(e.C=!1,i.C=!0,ek(this,i),e=i.R),e.L&&e.L.C||e.R&&e.R.C){e.R&&e.R.C||(e.L.C=!1,e.C=!0,nk(this,e),e=i.R),e.C=i.C,i.C=e.R.C=!1,ek(this,i),t=this._;break}}else if((e=i.L).C&&(e.C=!1,i.C=!0,nk(this,i),e=i.L),e.L&&e.L.C||e.R&&e.R.C){e.L&&e.L.C||(e.R.C=!1,e.C=!0,ek(this,e),e=i.L),e.C=i.C,i.C=e.L.C=!1,nk(this,i),t=this._;break}e.C=!0,t=i,i=i.U}while(!t.C);t&&(t.C=!1)}}};const ik=Jw;function ak(t,e,n,r){var i=[null,null],a=Mk.push(i)-1;return i.left=t,i.right=e,n&&sk(i,t,e,n),r&&sk(i,e,t,r),Sk[t.index].halfedges.push(a),Sk[e.index].halfedges.push(a),i}function ok(t,e,n){var r=[e,n];return r.left=t,r}function sk(t,e,n,r){t[0]||t[1]?t.left===n?t[1]=r:t[0]=r:(t[0]=r,t.left=e,t.right=n)}function ck(t,e,n,r,i){var a,o=t[0],s=t[1],c=o[0],u=o[1],l=0,h=1,f=s[0]-c,d=s[1]-u;if(a=e-c,f||!(a>0)){if(a/=f,f<0){if(a0){if(a>h)return;a>l&&(l=a)}if(a=r-c,f||!(a<0)){if(a/=f,f<0){if(a>h)return;a>l&&(l=a)}else if(f>0){if(a0)){if(a/=d,d<0){if(a0){if(a>h)return;a>l&&(l=a)}if(a=i-u,d||!(a<0)){if(a/=d,d<0){if(a>h)return;a>l&&(l=a)}else if(d>0){if(a0||h<1)||(l>0&&(t[0]=[c+l*f,u+l*d]),h<1&&(t[1]=[c+h*f,u+h*d]),!0)}}}}}function uk(t,e,n,r,i){var a=t[1];if(a)return!0;var o,s,c=t[0],u=t.left,l=t.right,h=u[0],f=u[1],d=l[0],p=l[1],y=(h+d)/2,g=(f+p)/2;if(p===f){if(y=r)return;if(h>d){if(c){if(c[1]>=i)return}else c=[y,n];a=[y,i]}else{if(c){if(c[1]1)if(h>d){if(c){if(c[1]>=i)return}else c=[(n-s)/o,n];a=[(i-s)/o,i]}else{if(c){if(c[1]=r)return}else c=[e,o*e+s];a=[r,o*r+s]}else{if(c){if(c[0]=-Dk)){var d=c*c+u*u,p=l*l+h*h,y=(h*d-u*p)/f,g=(c*p-l*d)/f,m=pk.pop()||new yk;m.arc=t,m.site=i,m.x=y+o,m.y=(m.cy=g+s)+Math.sqrt(y*y+g*g),t.circle=m;for(var v=null,b=Ak._;b;)if(m.yNk)s=s.L;else{if(!((i=a-Ek(s,o))>Nk)){r>-Nk?(e=s.P,n=s):i>-Nk?(e=s,n=s.N):e=n=s;break}if(!s.R){e=s;break}s=s.R}!function(t){Sk[t.index]={site:t,halfedges:[]}}(t);var c=_k(t);if(Ck.insert(e,c),e||n){if(e===n)return mk(e),n=_k(e.site),Ck.insert(c,n),c.edge=n.edge=ak(e.site,c.site),gk(e),void gk(n);if(n){mk(e),mk(n);var u=e.site,l=u[0],h=u[1],f=t[0]-l,d=t[1]-h,p=n.site,y=p[0]-l,g=p[1]-h,m=2*(f*g-d*y),v=f*f+d*d,b=y*y+g*g,_=[(g*v-d*b)/m+l,(f*b-y*v)/m+h];sk(n.edge,u,p,_),c.edge=ak(u,t,null,_),n.edge=ak(t,p,null,_),gk(e),gk(n)}else c.edge=ak(e.site,c.site)}}function Tk(t,e){var n=t.site,r=n[0],i=n[1],a=i-e;if(!a)return r;var o=t.P;if(!o)return-1/0;var s=(n=o.site)[0],c=n[1],u=c-e;if(!u)return s;var l=s-r,h=1/a-1/u,f=l/u;return h?(-f+Math.sqrt(f*f-2*h*(l*l/(-2*u)-c+u/2+i-a/2)))/h+r:(r+s)/2}function Ek(t,e){var n=t.N;if(n)return Tk(n,e);var r=t.site;return r[1]===e?r[0]:1/0}var Ck,Sk,Ak,Mk,Nk=1e-6,Dk=1e-12;function Ok(t,e,n){return(t[0]-n[0])*(e[1]-t[1])-(t[0]-e[0])*(n[1]-t[1])}function Bk(t,e){return e[1]-t[1]||e[0]-t[0]}function Lk(t,e){var n,r,i,a=t.sort(Bk).pop();for(Mk=[],Sk=new Array(t.length),Ck=new ik,Ak=new ik;;)if(i=dk,a&&(!i||a[1]Nk||Math.abs(i[0][1]-i[1][1])>Nk)||delete Mk[a]}(o,s,c,u),function(t,e,n,r){var i,a,o,s,c,u,l,h,f,d,p,y,g=Sk.length,m=!0;for(i=0;iNk||Math.abs(y-f)>Nk)&&(c.splice(s,0,Mk.push(ok(o,d,Math.abs(p-t)Nk?[t,Math.abs(h-t)Nk?[Math.abs(f-r)Nk?[n,Math.abs(h-n)Nk?[Math.abs(f-e)=s)return null;var c=t-i.site[0],u=e-i.site[1],l=c*c+u*u;do{i=a.cells[r=o],o=null,i.halfedges.forEach((function(n){var r=a.edges[n],s=r.left;if(s!==i.site&&s||(s=r.right)){var c=t-s[0],u=e-s[1],h=c*c+u*u;hr?(r+i)/2:Math.min(0,r)||Math.max(0,i),o>a?(a+o)/2:Math.min(0,a)||Math.max(0,o))}function Xk(){var t,e,n=qk,r=Hk,i=Gk,a=Wk,o=Vk,s=[0,1/0],c=[[-1/0,-1/0],[1/0,1/0]],u=250,l=mp,h=ht("start","zoom","end"),f=500,d=0;function p(t){t.property("__zoom",$k).on("wheel.zoom",x).on("mousedown.zoom",w).on("dblclick.zoom",k).filter(o).on("touchstart.zoom",T).on("touchmove.zoom",E).on("touchend.zoom touchcancel.zoom",C).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function y(t,e){return(e=Math.max(s[0],Math.min(s[1],e)))===t.k?t:new Pk(e,t.x,t.y)}function g(t,e,n){var r=e[0]-n[0]*t.k,i=e[1]-n[1]*t.k;return r===t.x&&i===t.y?t:new Pk(t.k,r,i)}function m(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function v(t,e,n){t.on("start.zoom",(function(){b(this,arguments).start()})).on("interrupt.zoom end.zoom",(function(){b(this,arguments).end()})).tween("zoom",(function(){var t=this,i=arguments,a=b(t,i),o=r.apply(t,i),s=null==n?m(o):"function"==typeof n?n.apply(t,i):n,c=Math.max(o[1][0]-o[0][0],o[1][1]-o[0][1]),u=t.__zoom,h="function"==typeof e?e.apply(t,i):e,f=l(u.invert(s).concat(c/u.k),h.invert(s).concat(c/h.k));return function(t){if(1===t)t=h;else{var e=f(t),n=c/e[2];t=new Pk(n,s[0]-e[0]*n,s[1]-e[1]*n)}a.zoom(null,t)}}))}function b(t,e,n){return!n&&t.__zooming||new _(t,e)}function _(t,e){this.that=t,this.args=e,this.active=0,this.extent=r.apply(t,e),this.taps=0}function x(){if(n.apply(this,arguments)){var t=b(this,arguments),e=this.__zoom,r=Math.max(s[0],Math.min(s[1],e.k*Math.pow(2,a.apply(this,arguments)))),o=Dn(this);if(t.wheel)t.mouse[0][0]===o[0]&&t.mouse[0][1]===o[1]||(t.mouse[1]=e.invert(t.mouse[0]=o)),clearTimeout(t.wheel);else{if(e.k===r)return;t.mouse=[o,e.invert(o)],rr(this),t.start()}Uk(),t.wheel=setTimeout((function(){t.wheel=null,t.end()}),150),t.zoom("mouse",i(g(y(e,r),t.mouse[0],t.mouse[1]),t.extent,c))}}function w(){if(!e&&n.apply(this,arguments)){var t=b(this,arguments,!0),r=we(ue.view).on("mousemove.zoom",(function(){if(Uk(),!t.moved){var e=ue.clientX-o,n=ue.clientY-s;t.moved=e*e+n*n>d}t.zoom("mouse",i(g(t.that.__zoom,t.mouse[0]=Dn(t.that),t.mouse[1]),t.extent,c))}),!0).on("mouseup.zoom",(function(){r.on("mousemove.zoom mouseup.zoom",null),Ce(ue.view,t.moved),Uk(),t.end()}),!0),a=Dn(this),o=ue.clientX,s=ue.clientY;Ee(ue.view),zk(),t.mouse=[a,this.__zoom.invert(a)],rr(this),t.start()}}function k(){if(n.apply(this,arguments)){var t=this.__zoom,e=Dn(this),a=t.invert(e),o=t.k*(ue.shiftKey?.5:2),s=i(g(y(t,o),e,a),r.apply(this,arguments),c);Uk(),u>0?we(this).transition().duration(u).call(v,s,e):we(this).call(p.transform,s)}}function T(){if(n.apply(this,arguments)){var e,r,i,a,o=ue.touches,s=o.length,c=b(this,arguments,ue.changedTouches.length===s);for(zk(),r=0;r{t.exports={graphlib:n(574),layout:n(8123),debug:n(7570),util:{time:n(1138).time,notime:n(1138).notime},version:n(8177)}},1207:(t,e,n)=>{var r=n(8436),i=n(4079);t.exports={run:function(t){var e="greedy"===t.graph().acyclicer?i(t,function(t){return function(e){return t.edge(e).weight}}(t)):function(t){var e=[],n={},i={};return r.forEach(t.nodes(),(function a(o){r.has(i,o)||(i[o]=!0,n[o]=!0,r.forEach(t.outEdges(o),(function(t){r.has(n,t.w)?e.push(t):a(t.w)})),delete n[o])})),e}(t);r.forEach(e,(function(e){var n=t.edge(e);t.removeEdge(e),n.forwardName=e.name,n.reversed=!0,t.setEdge(e.w,e.v,n,r.uniqueId("rev"))}))},undo:function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(n.reversed){t.removeEdge(e);var r=n.forwardName;delete n.reversed,delete n.forwardName,t.setEdge(e.w,e.v,n,r)}}))}}},1133:(t,e,n)=>{var r=n(8436),i=n(1138);function a(t,e,n,r,a,o){var s={width:0,height:0,rank:o,borderType:e},c=a[e][o-1],u=i.addDummyNode(t,"border",s,n);a[e][o]=u,t.setParent(u,r),c&&t.setEdge(c,u,{weight:1})}t.exports=function(t){r.forEach(t.children(),(function e(n){var i=t.children(n),o=t.node(n);if(i.length&&r.forEach(i,e),r.has(o,"minRank")){o.borderLeft=[],o.borderRight=[];for(var s=o.minRank,c=o.maxRank+1;s{var r=n(8436);function i(t){r.forEach(t.nodes(),(function(e){a(t.node(e))})),r.forEach(t.edges(),(function(e){a(t.edge(e))}))}function a(t){var e=t.width;t.width=t.height,t.height=e}function o(t){t.y=-t.y}function s(t){var e=t.x;t.x=t.y,t.y=e}t.exports={adjust:function(t){var e=t.graph().rankdir.toLowerCase();"lr"!==e&&"rl"!==e||i(t)},undo:function(t){var e=t.graph().rankdir.toLowerCase();"bt"!==e&&"rl"!==e||function(t){r.forEach(t.nodes(),(function(e){o(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.forEach(n.points,o),r.has(n,"y")&&o(n)}))}(t),"lr"!==e&&"rl"!==e||(function(t){r.forEach(t.nodes(),(function(e){s(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.forEach(n.points,s),r.has(n,"x")&&s(n)}))}(t),i(t))}}},7822:t=>{function e(){var t={};t._next=t._prev=t,this._sentinel=t}function n(t){t._prev._next=t._next,t._next._prev=t._prev,delete t._next,delete t._prev}function r(t,e){if("_next"!==t&&"_prev"!==t)return e}t.exports=e,e.prototype.dequeue=function(){var t=this._sentinel,e=t._prev;if(e!==t)return n(e),e},e.prototype.enqueue=function(t){var e=this._sentinel;t._prev&&t._next&&n(t),t._next=e._next,e._next._prev=t,e._next=t,t._prev=e},e.prototype.toString=function(){for(var t=[],e=this._sentinel,n=e._prev;n!==e;)t.push(JSON.stringify(n,r)),n=n._prev;return"["+t.join(", ")+"]"}},7570:(t,e,n)=>{var r=n(8436),i=n(1138),a=n(574).Graph;t.exports={debugOrdering:function(t){var e=i.buildLayerMatrix(t),n=new a({compound:!0,multigraph:!0}).setGraph({});return r.forEach(t.nodes(),(function(e){n.setNode(e,{label:e}),n.setParent(e,"layer"+t.node(e).rank)})),r.forEach(t.edges(),(function(t){n.setEdge(t.v,t.w,{},t.name)})),r.forEach(e,(function(t,e){var i="layer"+e;n.setNode(i,{rank:"same"}),r.reduce(t,(function(t,e){return n.setEdge(t,e,{style:"invis"}),e}))})),n}}},574:(t,e,n)=>{var r;try{r=n(8282)}catch(t){}r||(r=window.graphlib),t.exports=r},4079:(t,e,n)=>{var r=n(8436),i=n(574).Graph,a=n(7822);t.exports=function(t,e){if(t.nodeCount()<=1)return[];var n=function(t,e){var n=new i,o=0,s=0;r.forEach(t.nodes(),(function(t){n.setNode(t,{v:t,in:0,out:0})})),r.forEach(t.edges(),(function(t){var r=n.edge(t.v,t.w)||0,i=e(t),a=r+i;n.setEdge(t.v,t.w,a),s=Math.max(s,n.node(t.v).out+=i),o=Math.max(o,n.node(t.w).in+=i)}));var u=r.range(s+o+3).map((function(){return new a})),l=o+1;return r.forEach(n.nodes(),(function(t){c(u,l,n.node(t))})),{graph:n,buckets:u,zeroIdx:l}}(t,e||o),u=function(t,e,n){for(var r,i=[],a=e[e.length-1],o=e[0];t.nodeCount();){for(;r=o.dequeue();)s(t,e,n,r);for(;r=a.dequeue();)s(t,e,n,r);if(t.nodeCount())for(var c=e.length-2;c>0;--c)if(r=e[c].dequeue()){i=i.concat(s(t,e,n,r,!0));break}}return i}(n.graph,n.buckets,n.zeroIdx);return r.flatten(r.map(u,(function(e){return t.outEdges(e.v,e.w)})),!0)};var o=r.constant(1);function s(t,e,n,i,a){var o=a?[]:void 0;return r.forEach(t.inEdges(i.v),(function(r){var i=t.edge(r),s=t.node(r.v);a&&o.push({v:r.v,w:r.w}),s.out-=i,c(e,n,s)})),r.forEach(t.outEdges(i.v),(function(r){var i=t.edge(r),a=r.w,o=t.node(a);o.in-=i,c(e,n,o)})),t.removeNode(i.v),o}function c(t,e,n){n.out?n.in?t[n.out-n.in+e].enqueue(n):t[t.length-1].enqueue(n):t[0].enqueue(n)}},8123:(t,e,n)=>{var r=n(8436),i=n(1207),a=n(5995),o=n(8093),s=n(1138).normalizeRanks,c=n(4219),u=n(1138).removeEmptyRanks,l=n(2981),h=n(1133),f=n(3258),d=n(3408),p=n(7873),y=n(1138),g=n(574).Graph;t.exports=function(t,e){var n=e&&e.debugTiming?y.time:y.notime;n("layout",(function(){var e=n(" buildLayoutGraph",(function(){return function(t){var e=new g({multigraph:!0,compound:!0}),n=C(t.graph());return e.setGraph(r.merge({},v,E(n,m),r.pick(n,b))),r.forEach(t.nodes(),(function(n){var i=C(t.node(n));e.setNode(n,r.defaults(E(i,_),x)),e.setParent(n,t.parent(n))})),r.forEach(t.edges(),(function(n){var i=C(t.edge(n));e.setEdge(n,r.merge({},k,E(i,w),r.pick(i,T)))})),e}(t)}));n(" runLayout",(function(){!function(t,e){e(" makeSpaceForEdgeLabels",(function(){!function(t){var e=t.graph();e.ranksep/=2,r.forEach(t.edges(),(function(n){var r=t.edge(n);r.minlen*=2,"c"!==r.labelpos.toLowerCase()&&("TB"===e.rankdir||"BT"===e.rankdir?r.width+=r.labeloffset:r.height+=r.labeloffset)}))}(t)})),e(" removeSelfEdges",(function(){!function(t){r.forEach(t.edges(),(function(e){if(e.v===e.w){var n=t.node(e.v);n.selfEdges||(n.selfEdges=[]),n.selfEdges.push({e:e,label:t.edge(e)}),t.removeEdge(e)}}))}(t)})),e(" acyclic",(function(){i.run(t)})),e(" nestingGraph.run",(function(){l.run(t)})),e(" rank",(function(){o(y.asNonCompoundGraph(t))})),e(" injectEdgeLabelProxies",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(n.width&&n.height){var r=t.node(e.v),i={rank:(t.node(e.w).rank-r.rank)/2+r.rank,e:e};y.addDummyNode(t,"edge-proxy",i,"_ep")}}))}(t)})),e(" removeEmptyRanks",(function(){u(t)})),e(" nestingGraph.cleanup",(function(){l.cleanup(t)})),e(" normalizeRanks",(function(){s(t)})),e(" assignRankMinMax",(function(){!function(t){var e=0;r.forEach(t.nodes(),(function(n){var i=t.node(n);i.borderTop&&(i.minRank=t.node(i.borderTop).rank,i.maxRank=t.node(i.borderBottom).rank,e=r.max(e,i.maxRank))})),t.graph().maxRank=e}(t)})),e(" removeEdgeLabelProxies",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);"edge-proxy"===n.dummy&&(t.edge(n.e).labelRank=n.rank,t.removeNode(e))}))}(t)})),e(" normalize.run",(function(){a.run(t)})),e(" parentDummyChains",(function(){c(t)})),e(" addBorderSegments",(function(){h(t)})),e(" order",(function(){d(t)})),e(" insertSelfEdges",(function(){!function(t){var e=y.buildLayerMatrix(t);r.forEach(e,(function(e){var n=0;r.forEach(e,(function(e,i){var a=t.node(e);a.order=i+n,r.forEach(a.selfEdges,(function(e){y.addDummyNode(t,"selfedge",{width:e.label.width,height:e.label.height,rank:a.rank,order:i+ ++n,e:e.e,label:e.label},"_se")})),delete a.selfEdges}))}))}(t)})),e(" adjustCoordinateSystem",(function(){f.adjust(t)})),e(" position",(function(){p(t)})),e(" positionSelfEdges",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);if("selfedge"===n.dummy){var r=t.node(n.e.v),i=r.x+r.width/2,a=r.y,o=n.x-i,s=r.height/2;t.setEdge(n.e,n.label),t.removeNode(e),n.label.points=[{x:i+2*o/3,y:a-s},{x:i+5*o/6,y:a-s},{x:i+o,y:a},{x:i+5*o/6,y:a+s},{x:i+2*o/3,y:a+s}],n.label.x=n.x,n.label.y=n.y}}))}(t)})),e(" removeBorderNodes",(function(){!function(t){r.forEach(t.nodes(),(function(e){if(t.children(e).length){var n=t.node(e),i=t.node(n.borderTop),a=t.node(n.borderBottom),o=t.node(r.last(n.borderLeft)),s=t.node(r.last(n.borderRight));n.width=Math.abs(s.x-o.x),n.height=Math.abs(a.y-i.y),n.x=o.x+n.width/2,n.y=i.y+n.height/2}})),r.forEach(t.nodes(),(function(e){"border"===t.node(e).dummy&&t.removeNode(e)}))}(t)})),e(" normalize.undo",(function(){a.undo(t)})),e(" fixupEdgeLabelCoords",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(r.has(n,"x"))switch("l"!==n.labelpos&&"r"!==n.labelpos||(n.width-=n.labeloffset),n.labelpos){case"l":n.x-=n.width/2+n.labeloffset;break;case"r":n.x+=n.width/2+n.labeloffset}}))}(t)})),e(" undoCoordinateSystem",(function(){f.undo(t)})),e(" translateGraph",(function(){!function(t){var e=Number.POSITIVE_INFINITY,n=0,i=Number.POSITIVE_INFINITY,a=0,o=t.graph(),s=o.marginx||0,c=o.marginy||0;function u(t){var r=t.x,o=t.y,s=t.width,c=t.height;e=Math.min(e,r-s/2),n=Math.max(n,r+s/2),i=Math.min(i,o-c/2),a=Math.max(a,o+c/2)}r.forEach(t.nodes(),(function(e){u(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.has(n,"x")&&u(n)})),e-=s,i-=c,r.forEach(t.nodes(),(function(n){var r=t.node(n);r.x-=e,r.y-=i})),r.forEach(t.edges(),(function(n){var a=t.edge(n);r.forEach(a.points,(function(t){t.x-=e,t.y-=i})),r.has(a,"x")&&(a.x-=e),r.has(a,"y")&&(a.y-=i)})),o.width=n-e+s,o.height=a-i+c}(t)})),e(" assignNodeIntersects",(function(){!function(t){r.forEach(t.edges(),(function(e){var n,r,i=t.edge(e),a=t.node(e.v),o=t.node(e.w);i.points?(n=i.points[0],r=i.points[i.points.length-1]):(i.points=[],n=o,r=a),i.points.unshift(y.intersectRect(a,n)),i.points.push(y.intersectRect(o,r))}))}(t)})),e(" reversePoints",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);n.reversed&&n.points.reverse()}))}(t)})),e(" acyclic.undo",(function(){i.undo(t)}))}(e,n)})),n(" updateInputGraph",(function(){!function(t,e){r.forEach(t.nodes(),(function(n){var r=t.node(n),i=e.node(n);r&&(r.x=i.x,r.y=i.y,e.children(n).length&&(r.width=i.width,r.height=i.height))})),r.forEach(t.edges(),(function(n){var i=t.edge(n),a=e.edge(n);i.points=a.points,r.has(a,"x")&&(i.x=a.x,i.y=a.y)})),t.graph().width=e.graph().width,t.graph().height=e.graph().height}(t,e)}))}))};var m=["nodesep","edgesep","ranksep","marginx","marginy"],v={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},b=["acyclicer","ranker","rankdir","align"],_=["width","height"],x={width:0,height:0},w=["minlen","weight","width","height","labeloffset"],k={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},T=["labelpos"];function E(t,e){return r.mapValues(r.pick(t,e),Number)}function C(t){var e={};return r.forEach(t,(function(t,n){e[n.toLowerCase()]=t})),e}},8436:(t,e,n)=>{var r;try{r={cloneDeep:n(361),constant:n(5703),defaults:n(1747),each:n(6073),filter:n(3105),find:n(3311),flatten:n(5564),forEach:n(4486),forIn:n(2620),has:n(8721),isUndefined:n(2353),last:n(928),map:n(5161),mapValues:n(6604),max:n(6162),merge:n(3857),min:n(3632),minBy:n(2762),now:n(7771),pick:n(9722),range:n(6026),reduce:n(4061),sortBy:n(9734),uniqueId:n(3955),values:n(2628),zipObject:n(7287)}}catch(t){}r||(r=window._),t.exports=r},2981:(t,e,n)=>{var r=n(8436),i=n(1138);function a(t,e,n,o,s,c,u){var l=t.children(u);if(l.length){var h=i.addBorderNode(t,"_bt"),f=i.addBorderNode(t,"_bb"),d=t.node(u);t.setParent(h,u),d.borderTop=h,t.setParent(f,u),d.borderBottom=f,r.forEach(l,(function(r){a(t,e,n,o,s,c,r);var i=t.node(r),l=i.borderTop?i.borderTop:r,d=i.borderBottom?i.borderBottom:r,p=i.borderTop?o:2*o,y=l!==d?1:s-c[u]+1;t.setEdge(h,l,{weight:p,minlen:y,nestingEdge:!0}),t.setEdge(d,f,{weight:p,minlen:y,nestingEdge:!0})})),t.parent(u)||t.setEdge(e,h,{weight:0,minlen:s+c[u]})}else u!==e&&t.setEdge(e,u,{weight:0,minlen:n})}t.exports={run:function(t){var e=i.addDummyNode(t,"root",{},"_root"),n=function(t){var e={};function n(i,a){var o=t.children(i);o&&o.length&&r.forEach(o,(function(t){n(t,a+1)})),e[i]=a}return r.forEach(t.children(),(function(t){n(t,1)})),e}(t),o=r.max(r.values(n))-1,s=2*o+1;t.graph().nestingRoot=e,r.forEach(t.edges(),(function(e){t.edge(e).minlen*=s}));var c=function(t){return r.reduce(t.edges(),(function(e,n){return e+t.edge(n).weight}),0)}(t)+1;r.forEach(t.children(),(function(r){a(t,e,s,c,o,n,r)})),t.graph().nodeRankFactor=s},cleanup:function(t){var e=t.graph();t.removeNode(e.nestingRoot),delete e.nestingRoot,r.forEach(t.edges(),(function(e){t.edge(e).nestingEdge&&t.removeEdge(e)}))}}},5995:(t,e,n)=>{var r=n(8436),i=n(1138);t.exports={run:function(t){t.graph().dummyChains=[],r.forEach(t.edges(),(function(e){!function(t,e){var n,r,a,o=e.v,s=t.node(o).rank,c=e.w,u=t.node(c).rank,l=e.name,h=t.edge(e),f=h.labelRank;if(u!==s+1){for(t.removeEdge(e),a=0,++s;s{var r=n(8436);t.exports=function(t,e,n){var i,a={};r.forEach(n,(function(n){for(var r,o,s=t.parent(n);s;){if((r=t.parent(s))?(o=a[r],a[r]=s):(o=i,i=s),o&&o!==s)return void e.setEdge(o,s);s=r}}))}},5439:(t,e,n)=>{var r=n(8436);t.exports=function(t,e){return r.map(e,(function(e){var n=t.inEdges(e);if(n.length){var i=r.reduce(n,(function(e,n){var r=t.edge(n),i=t.node(n.v);return{sum:e.sum+r.weight*i.order,weight:e.weight+r.weight}}),{sum:0,weight:0});return{v:e,barycenter:i.sum/i.weight,weight:i.weight}}return{v:e}}))}},3128:(t,e,n)=>{var r=n(8436),i=n(574).Graph;t.exports=function(t,e,n){var a=function(t){for(var e;t.hasNode(e=r.uniqueId("_root")););return e}(t),o=new i({compound:!0}).setGraph({root:a}).setDefaultNodeLabel((function(e){return t.node(e)}));return r.forEach(t.nodes(),(function(i){var s=t.node(i),c=t.parent(i);(s.rank===e||s.minRank<=e&&e<=s.maxRank)&&(o.setNode(i),o.setParent(i,c||a),r.forEach(t[n](i),(function(e){var n=e.v===i?e.w:e.v,a=o.edge(n,i),s=r.isUndefined(a)?0:a.weight;o.setEdge(n,i,{weight:t.edge(e).weight+s})})),r.has(s,"minRank")&&o.setNode(i,{borderLeft:s.borderLeft[e],borderRight:s.borderRight[e]}))})),o}},6630:(t,e,n)=>{var r=n(8436);function i(t,e,n){for(var i=r.zipObject(n,r.map(n,(function(t,e){return e}))),a=r.flatten(r.map(e,(function(e){return r.sortBy(r.map(t.outEdges(e),(function(e){return{pos:i[e.w],weight:t.edge(e).weight}})),"pos")})),!0),o=1;o0;)e%2&&(n+=c[e+1]),c[e=e-1>>1]+=t.weight;u+=t.weight*n}))),u}t.exports=function(t,e){for(var n=0,r=1;r{var r=n(8436),i=n(2588),a=n(6630),o=n(1026),s=n(3128),c=n(5093),u=n(574).Graph,l=n(1138);function h(t,e,n){return r.map(e,(function(e){return s(t,e,n)}))}function f(t,e){var n=new u;r.forEach(t,(function(t){var i=t.graph().root,a=o(t,i,n,e);r.forEach(a.vs,(function(e,n){t.node(e).order=n})),c(t,n,a.vs)}))}function d(t,e){r.forEach(e,(function(e){r.forEach(e,(function(e,n){t.node(e).order=n}))}))}t.exports=function(t){var e=l.maxRank(t),n=h(t,r.range(1,e+1),"inEdges"),o=h(t,r.range(e-1,-1,-1),"outEdges"),s=i(t);d(t,s);for(var c,u=Number.POSITIVE_INFINITY,p=0,y=0;y<4;++p,++y){f(p%2?n:o,p%4>=2),s=l.buildLayerMatrix(t);var g=a(t,s);g{var r=n(8436);t.exports=function(t){var e={},n=r.filter(t.nodes(),(function(e){return!t.children(e).length})),i=r.max(r.map(n,(function(e){return t.node(e).rank}))),a=r.map(r.range(i+1),(function(){return[]})),o=r.sortBy(n,(function(e){return t.node(e).rank}));return r.forEach(o,(function n(i){if(!r.has(e,i)){e[i]=!0;var o=t.node(i);a[o.rank].push(i),r.forEach(t.successors(i),n)}})),a}},9567:(t,e,n)=>{var r=n(8436);t.exports=function(t,e){var n={};return r.forEach(t,(function(t,e){var i=n[t.v]={indegree:0,in:[],out:[],vs:[t.v],i:e};r.isUndefined(t.barycenter)||(i.barycenter=t.barycenter,i.weight=t.weight)})),r.forEach(e.edges(),(function(t){var e=n[t.v],i=n[t.w];r.isUndefined(e)||r.isUndefined(i)||(i.indegree++,e.out.push(n[t.w]))})),function(t){var e=[];function n(t){return function(e){var n,i,a,o;e.merged||(r.isUndefined(e.barycenter)||r.isUndefined(t.barycenter)||e.barycenter>=t.barycenter)&&(i=e,a=0,o=0,(n=t).weight&&(a+=n.barycenter*n.weight,o+=n.weight),i.weight&&(a+=i.barycenter*i.weight,o+=i.weight),n.vs=i.vs.concat(n.vs),n.barycenter=a/o,n.weight=o,n.i=Math.min(i.i,n.i),i.merged=!0)}}function i(e){return function(n){n.in.push(e),0==--n.indegree&&t.push(n)}}for(;t.length;){var a=t.pop();e.push(a),r.forEach(a.in.reverse(),n(a)),r.forEach(a.out,i(a))}return r.map(r.filter(e,(function(t){return!t.merged})),(function(t){return r.pick(t,["vs","i","barycenter","weight"])}))}(r.filter(n,(function(t){return!t.indegree})))}},1026:(t,e,n)=>{var r=n(8436),i=n(5439),a=n(9567),o=n(7304);t.exports=function t(e,n,s,c){var u=e.children(n),l=e.node(n),h=l?l.borderLeft:void 0,f=l?l.borderRight:void 0,d={};h&&(u=r.filter(u,(function(t){return t!==h&&t!==f})));var p=i(e,u);r.forEach(p,(function(n){if(e.children(n.v).length){var i=t(e,n.v,s,c);d[n.v]=i,r.has(i,"barycenter")&&(a=n,o=i,r.isUndefined(a.barycenter)?(a.barycenter=o.barycenter,a.weight=o.weight):(a.barycenter=(a.barycenter*a.weight+o.barycenter*o.weight)/(a.weight+o.weight),a.weight+=o.weight))}var a,o}));var y=a(p,s);!function(t,e){r.forEach(t,(function(t){t.vs=r.flatten(t.vs.map((function(t){return e[t]?e[t].vs:t})),!0)}))}(y,d);var g=o(y,c);if(h&&(g.vs=r.flatten([h,g.vs,f],!0),e.predecessors(h).length)){var m=e.node(e.predecessors(h)[0]),v=e.node(e.predecessors(f)[0]);r.has(g,"barycenter")||(g.barycenter=0,g.weight=0),g.barycenter=(g.barycenter*g.weight+m.order+v.order)/(g.weight+2),g.weight+=2}return g}},7304:(t,e,n)=>{var r=n(8436),i=n(1138);function a(t,e,n){for(var i;e.length&&(i=r.last(e)).i<=n;)e.pop(),t.push(i.vs),n++;return n}t.exports=function(t,e){var n,o=i.partition(t,(function(t){return r.has(t,"barycenter")})),s=o.lhs,c=r.sortBy(o.rhs,(function(t){return-t.i})),u=[],l=0,h=0,f=0;s.sort((n=!!e,function(t,e){return t.barycentere.barycenter?1:n?e.i-t.i:t.i-e.i})),f=a(u,c,f),r.forEach(s,(function(t){f+=t.vs.length,u.push(t.vs),l+=t.barycenter*t.weight,h+=t.weight,f=a(u,c,f)}));var d={vs:r.flatten(u,!0)};return h&&(d.barycenter=l/h,d.weight=h),d}},4219:(t,e,n)=>{var r=n(8436);t.exports=function(t){var e=function(t){var e={},n=0;return r.forEach(t.children(),(function i(a){var o=n;r.forEach(t.children(a),i),e[a]={low:o,lim:n++}})),e}(t);r.forEach(t.graph().dummyChains,(function(n){for(var r=t.node(n),i=r.edgeObj,a=function(t,e,n,r){var i,a,o=[],s=[],c=Math.min(e[n].low,e[r].low),u=Math.max(e[n].lim,e[r].lim);i=n;do{i=t.parent(i),o.push(i)}while(i&&(e[i].low>c||u>e[i].lim));for(a=i,i=r;(i=t.parent(i))!==a;)s.push(i);return{path:o.concat(s.reverse()),lca:a}}(t,e,i.v,i.w),o=a.path,s=a.lca,c=0,u=o[c],l=!0;n!==i.w;){if(r=t.node(n),l){for(;(u=o[c])!==s&&t.node(u).maxRank{var r=n(8436),i=n(574).Graph,a=n(1138);function o(t,e){var n={};return r.reduce(e,(function(e,i){var a=0,o=0,s=e.length,u=r.last(i);return r.forEach(i,(function(e,l){var h=function(t,e){if(t.node(e).dummy)return r.find(t.predecessors(e),(function(e){return t.node(e).dummy}))}(t,e),f=h?t.node(h).order:s;(h||e===u)&&(r.forEach(i.slice(o,l+1),(function(e){r.forEach(t.predecessors(e),(function(r){var i=t.node(r),o=i.order;!(os)&&c(n,e,u)}))}))}return r.reduce(e,(function(e,n){var a,o=-1,s=0;return r.forEach(n,(function(r,c){if("border"===t.node(r).dummy){var u=t.predecessors(r);u.length&&(a=t.node(u[0]).order,i(n,s,c,o,a),s=c,o=a)}i(n,s,n.length,a,e.length)})),n})),n}function c(t,e,n){if(e>n){var r=e;e=n,n=r}var i=t[e];i||(t[e]=i={}),i[n]=!0}function u(t,e,n){if(e>n){var i=e;e=n,n=i}return r.has(t[e],n)}function l(t,e,n,i){var a={},o={},s={};return r.forEach(e,(function(t){r.forEach(t,(function(t,e){a[t]=t,o[t]=t,s[t]=e}))})),r.forEach(e,(function(t){var e=-1;r.forEach(t,(function(t){var c=i(t);if(c.length){c=r.sortBy(c,(function(t){return s[t]}));for(var l=(c.length-1)/2,h=Math.floor(l),f=Math.ceil(l);h<=f;++h){var d=c[h];o[t]===t&&e{var r=n(8436),i=n(1138),a=n(3573).positionX;t.exports=function(t){(function(t){var e=i.buildLayerMatrix(t),n=t.graph().ranksep,a=0;r.forEach(e,(function(e){var i=r.max(r.map(e,(function(e){return t.node(e).height})));r.forEach(e,(function(e){t.node(e).y=a+i/2})),a+=i+n}))})(t=i.asNonCompoundGraph(t)),r.forEach(a(t),(function(e,n){t.node(n).x=e}))}},300:(t,e,n)=>{var r=n(8436),i=n(574).Graph,a=n(6681).slack;function o(t,e){return r.forEach(t.nodes(),(function n(i){r.forEach(e.nodeEdges(i),(function(r){var o=r.v,s=i===o?r.w:o;t.hasNode(s)||a(e,r)||(t.setNode(s,{}),t.setEdge(i,s,{}),n(s))}))})),t.nodeCount()}function s(t,e){return r.minBy(e.edges(),(function(n){if(t.hasNode(n.v)!==t.hasNode(n.w))return a(e,n)}))}function c(t,e,n){r.forEach(t.nodes(),(function(t){e.node(t).rank+=n}))}t.exports=function(t){var e,n,r=new i({directed:!1}),u=t.nodes()[0],l=t.nodeCount();for(r.setNode(u,{});o(r,t){var r=n(6681).longestPath,i=n(300),a=n(2472);t.exports=function(t){switch(t.graph().ranker){case"network-simplex":default:!function(t){a(t)}(t);break;case"tight-tree":!function(t){r(t),i(t)}(t);break;case"longest-path":o(t)}};var o=r},2472:(t,e,n)=>{var r=n(8436),i=n(300),a=n(6681).slack,o=n(6681).longestPath,s=n(574).alg.preorder,c=n(574).alg.postorder,u=n(1138).simplify;function l(t){t=u(t),o(t);var e,n=i(t);for(d(n),h(n,t);e=y(n);)m(n,t,e,g(n,t,e))}function h(t,e){var n=c(t,t.nodes());n=n.slice(0,n.length-1),r.forEach(n,(function(n){!function(t,e,n){var r=t.node(n).parent;t.edge(n,r).cutvalue=f(t,e,n)}(t,e,n)}))}function f(t,e,n){var i=t.node(n).parent,a=!0,o=e.edge(n,i),s=0;return o||(a=!1,o=e.edge(i,n)),s=o.weight,r.forEach(e.nodeEdges(n),(function(r){var o,c,u=r.v===n,l=u?r.w:r.v;if(l!==i){var h=u===a,f=e.edge(r).weight;if(s+=h?f:-f,o=n,c=l,t.hasEdge(o,c)){var d=t.edge(n,l).cutvalue;s+=h?-d:d}}})),s}function d(t,e){arguments.length<2&&(e=t.nodes()[0]),p(t,{},1,e)}function p(t,e,n,i,a){var o=n,s=t.node(i);return e[i]=!0,r.forEach(t.neighbors(i),(function(a){r.has(e,a)||(n=p(t,e,n,a,i))})),s.low=o,s.lim=n++,a?s.parent=a:delete s.parent,n}function y(t){return r.find(t.edges(),(function(e){return t.edge(e).cutvalue<0}))}function g(t,e,n){var i=n.v,o=n.w;e.hasEdge(i,o)||(i=n.w,o=n.v);var s=t.node(i),c=t.node(o),u=s,l=!1;s.lim>c.lim&&(u=c,l=!0);var h=r.filter(e.edges(),(function(e){return l===v(0,t.node(e.v),u)&&l!==v(0,t.node(e.w),u)}));return r.minBy(h,(function(t){return a(e,t)}))}function m(t,e,n,i){var a=n.v,o=n.w;t.removeEdge(a,o),t.setEdge(i.v,i.w,{}),d(t),h(t,e),function(t,e){var n=r.find(t.nodes(),(function(t){return!e.node(t).parent})),i=s(t,n);i=i.slice(1),r.forEach(i,(function(n){var r=t.node(n).parent,i=e.edge(n,r),a=!1;i||(i=e.edge(r,n),a=!0),e.node(n).rank=e.node(r).rank+(a?i.minlen:-i.minlen)}))}(t,e)}function v(t,e,n){return n.low<=e.lim&&e.lim<=n.lim}t.exports=l,l.initLowLimValues=d,l.initCutValues=h,l.calcCutValue=f,l.leaveEdge=y,l.enterEdge=g,l.exchangeEdges=m},6681:(t,e,n)=>{var r=n(8436);t.exports={longestPath:function(t){var e={};r.forEach(t.sources(),(function n(i){var a=t.node(i);if(r.has(e,i))return a.rank;e[i]=!0;var o=r.min(r.map(t.outEdges(i),(function(e){return n(e.w)-t.edge(e).minlen})));return o!==Number.POSITIVE_INFINITY&&null!=o||(o=0),a.rank=o}))},slack:function(t,e){return t.node(e.w).rank-t.node(e.v).rank-t.edge(e).minlen}}},1138:(t,e,n)=>{var r=n(8436),i=n(574).Graph;function a(t,e,n,i){var a;do{a=r.uniqueId(i)}while(t.hasNode(a));return n.dummy=e,t.setNode(a,n),a}function o(t){return r.max(r.map(t.nodes(),(function(e){var n=t.node(e).rank;if(!r.isUndefined(n))return n})))}t.exports={addDummyNode:a,simplify:function(t){var e=(new i).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){var r=e.edge(n.v,n.w)||{weight:0,minlen:1},i=t.edge(n);e.setEdge(n.v,n.w,{weight:r.weight+i.weight,minlen:Math.max(r.minlen,i.minlen)})})),e},asNonCompoundGraph:function(t){var e=new i({multigraph:t.isMultigraph()}).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){t.children(n).length||e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){e.setEdge(n,t.edge(n))})),e},successorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.outEdges(e),(function(e){n[e.w]=(n[e.w]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},predecessorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.inEdges(e),(function(e){n[e.v]=(n[e.v]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},intersectRect:function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,c=t.width/2,u=t.height/2;if(!o&&!s)throw new Error("Not possible to find intersection inside of the rectangle");return Math.abs(s)*c>Math.abs(o)*u?(s<0&&(u=-u),n=u*o/s,r=u):(o<0&&(c=-c),n=c,r=c*s/o),{x:i+n,y:a+r}},buildLayerMatrix:function(t){var e=r.map(r.range(o(t)+1),(function(){return[]}));return r.forEach(t.nodes(),(function(n){var i=t.node(n),a=i.rank;r.isUndefined(a)||(e[a][i.order]=n)})),e},normalizeRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank})));r.forEach(t.nodes(),(function(n){var i=t.node(n);r.has(i,"rank")&&(i.rank-=e)}))},removeEmptyRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank}))),n=[];r.forEach(t.nodes(),(function(r){var i=t.node(r).rank-e;n[i]||(n[i]=[]),n[i].push(r)}));var i=0,a=t.graph().nodeRankFactor;r.forEach(n,(function(e,n){r.isUndefined(e)&&n%a!=0?--i:i&&r.forEach(e,(function(e){t.node(e).rank+=i}))}))},addBorderNode:function(t,e,n,r){var i={width:0,height:0};return arguments.length>=4&&(i.rank=n,i.order=r),a(t,"border",i,e)},maxRank:o,partition:function(t,e){var n={lhs:[],rhs:[]};return r.forEach(t,(function(t){e(t)?n.lhs.push(t):n.rhs.push(t)})),n},time:function(t,e){var n=r.now();try{return e()}finally{console.log(t+" time: "+(r.now()-n)+"ms")}},notime:function(t,e){return e()}}},8177:t=>{t.exports="0.8.5"},7856:function(t){t.exports=function(){var t=Object.hasOwnProperty,e=Object.setPrototypeOf,n=Object.isFrozen,r=Object.getPrototypeOf,i=Object.getOwnPropertyDescriptor,a=Object.freeze,o=Object.seal,s=Object.create,c="undefined"!=typeof Reflect&&Reflect,u=c.apply,l=c.construct;u||(u=function(t,e,n){return t.apply(e,n)}),a||(a=function(t){return t}),o||(o=function(t){return t}),l||(l=function(t,e){return new(Function.prototype.bind.apply(t,[null].concat(function(t){if(Array.isArray(t)){for(var e=0,n=Array(t.length);e1?n-1:0),i=1;i/gm),j=o(/^data-[\-\w.\u00B7-\uFFFF]/),Y=o(/^aria-[\-\w]+$/),z=o(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),U=o(/^(?:\w+script|data):/i),q=o(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),H="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};function $(t){if(Array.isArray(t)){for(var e=0,n=Array(t.length);e0&&void 0!==arguments[0]?arguments[0]:"undefined"==typeof window?null:window,n=function(e){return t(e)};if(n.version="2.3.5",n.removed=[],!e||!e.document||9!==e.document.nodeType)return n.isSupported=!1,n;var r=e.document,i=e.document,o=e.DocumentFragment,s=e.HTMLTemplateElement,c=e.Node,u=e.Element,l=e.NodeFilter,h=e.NamedNodeMap,w=void 0===h?e.NamedNodeMap||e.MozNamedAttrMap:h,W=e.HTMLFormElement,V=e.DOMParser,G=e.trustedTypes,X=u.prototype,Z=E(X,"cloneNode"),K=E(X,"nextSibling"),Q=E(X,"childNodes"),J=E(X,"parentNode");if("function"==typeof s){var tt=i.createElement("template");tt.content&&tt.content.ownerDocument&&(i=tt.content.ownerDocument)}var et=function(t,e){if("object"!==(void 0===t?"undefined":H(t))||"function"!=typeof t.createPolicy)return null;var n=null,r="data-tt-policy-suffix";e.currentScript&&e.currentScript.hasAttribute(r)&&(n=e.currentScript.getAttribute(r));var i="dompurify"+(n?"#"+n:"");try{return t.createPolicy(i,{createHTML:function(t){return t}})}catch(t){return console.warn("TrustedTypes policy "+i+" could not be created."),null}}(G,r),nt=et?et.createHTML(""):"",rt=i,it=rt.implementation,at=rt.createNodeIterator,ot=rt.createDocumentFragment,st=rt.getElementsByTagName,ct=r.importNode,ut={};try{ut=T(i).documentMode?i.documentMode:{}}catch(t){}var lt={};n.isSupported="function"==typeof J&&it&&void 0!==it.createHTMLDocument&&9!==ut;var ht=F,ft=P,dt=j,pt=Y,yt=U,gt=q,mt=z,vt=null,bt=k({},[].concat($(C),$(S),$(A),$(N),$(O))),_t=null,xt=k({},[].concat($(B),$(L),$(I),$(R))),wt=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),kt=null,Tt=null,Et=!0,Ct=!0,St=!1,At=!1,Mt=!1,Nt=!1,Dt=!1,Ot=!1,Bt=!1,Lt=!1,It=!0,Rt=!0,Ft=!1,Pt={},jt=null,Yt=k({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),zt=null,Ut=k({},["audio","video","img","source","image","track"]),qt=null,Ht=k({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),$t="http://www.w3.org/1998/Math/MathML",Wt="http://www.w3.org/2000/svg",Vt="http://www.w3.org/1999/xhtml",Gt=Vt,Xt=!1,Zt=void 0,Kt=["application/xhtml+xml","text/html"],Qt=void 0,Jt=null,te=i.createElement("form"),ee=function(t){return t instanceof RegExp||t instanceof Function},ne=function(t){Jt&&Jt===t||(t&&"object"===(void 0===t?"undefined":H(t))||(t={}),t=T(t),vt="ALLOWED_TAGS"in t?k({},t.ALLOWED_TAGS):bt,_t="ALLOWED_ATTR"in t?k({},t.ALLOWED_ATTR):xt,qt="ADD_URI_SAFE_ATTR"in t?k(T(Ht),t.ADD_URI_SAFE_ATTR):Ht,zt="ADD_DATA_URI_TAGS"in t?k(T(Ut),t.ADD_DATA_URI_TAGS):Ut,jt="FORBID_CONTENTS"in t?k({},t.FORBID_CONTENTS):Yt,kt="FORBID_TAGS"in t?k({},t.FORBID_TAGS):{},Tt="FORBID_ATTR"in t?k({},t.FORBID_ATTR):{},Pt="USE_PROFILES"in t&&t.USE_PROFILES,Et=!1!==t.ALLOW_ARIA_ATTR,Ct=!1!==t.ALLOW_DATA_ATTR,St=t.ALLOW_UNKNOWN_PROTOCOLS||!1,At=t.SAFE_FOR_TEMPLATES||!1,Mt=t.WHOLE_DOCUMENT||!1,Ot=t.RETURN_DOM||!1,Bt=t.RETURN_DOM_FRAGMENT||!1,Lt=t.RETURN_TRUSTED_TYPE||!1,Dt=t.FORCE_BODY||!1,It=!1!==t.SANITIZE_DOM,Rt=!1!==t.KEEP_CONTENT,Ft=t.IN_PLACE||!1,mt=t.ALLOWED_URI_REGEXP||mt,Gt=t.NAMESPACE||Vt,t.CUSTOM_ELEMENT_HANDLING&&ee(t.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(wt.tagNameCheck=t.CUSTOM_ELEMENT_HANDLING.tagNameCheck),t.CUSTOM_ELEMENT_HANDLING&&ee(t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(wt.attributeNameCheck=t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),t.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(wt.allowCustomizedBuiltInElements=t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Zt=Zt=-1===Kt.indexOf(t.PARSER_MEDIA_TYPE)?"text/html":t.PARSER_MEDIA_TYPE,Qt="application/xhtml+xml"===Zt?function(t){return t}:y,At&&(Ct=!1),Bt&&(Ot=!0),Pt&&(vt=k({},[].concat($(O))),_t=[],!0===Pt.html&&(k(vt,C),k(_t,B)),!0===Pt.svg&&(k(vt,S),k(_t,L),k(_t,R)),!0===Pt.svgFilters&&(k(vt,A),k(_t,L),k(_t,R)),!0===Pt.mathMl&&(k(vt,N),k(_t,I),k(_t,R))),t.ADD_TAGS&&(vt===bt&&(vt=T(vt)),k(vt,t.ADD_TAGS)),t.ADD_ATTR&&(_t===xt&&(_t=T(_t)),k(_t,t.ADD_ATTR)),t.ADD_URI_SAFE_ATTR&&k(qt,t.ADD_URI_SAFE_ATTR),t.FORBID_CONTENTS&&(jt===Yt&&(jt=T(jt)),k(jt,t.FORBID_CONTENTS)),Rt&&(vt["#text"]=!0),Mt&&k(vt,["html","head","body"]),vt.table&&(k(vt,["tbody"]),delete kt.tbody),a&&a(t),Jt=t)},re=k({},["mi","mo","mn","ms","mtext"]),ie=k({},["foreignobject","desc","title","annotation-xml"]),ae=k({},S);k(ae,A),k(ae,M);var oe=k({},N);k(oe,D);var se=function(t){p(n.removed,{element:t});try{t.parentNode.removeChild(t)}catch(e){try{t.outerHTML=nt}catch(e){t.remove()}}},ce=function(t,e){try{p(n.removed,{attribute:e.getAttributeNode(t),from:e})}catch(t){p(n.removed,{attribute:null,from:e})}if(e.removeAttribute(t),"is"===t&&!_t[t])if(Ot||Bt)try{se(e)}catch(t){}else try{e.setAttribute(t,"")}catch(t){}},ue=function(t){var e=void 0,n=void 0;if(Dt)t=""+t;else{var r=g(t,/^[\r\n\t ]+/);n=r&&r[0]}"application/xhtml+xml"===Zt&&(t=''+t+"");var a=et?et.createHTML(t):t;if(Gt===Vt)try{e=(new V).parseFromString(a,Zt)}catch(t){}if(!e||!e.documentElement){e=it.createDocument(Gt,"template",null);try{e.documentElement.innerHTML=Xt?"":a}catch(t){}}var o=e.body||e.documentElement;return t&&n&&o.insertBefore(i.createTextNode(n),o.childNodes[0]||null),Gt===Vt?st.call(e,Mt?"html":"body")[0]:Mt?e.documentElement:o},le=function(t){return at.call(t.ownerDocument||t,t,l.SHOW_ELEMENT|l.SHOW_COMMENT|l.SHOW_TEXT,null,!1)},he=function(t){return"object"===(void 0===c?"undefined":H(c))?t instanceof c:t&&"object"===(void 0===t?"undefined":H(t))&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName},fe=function(t,e,r){lt[t]&&f(lt[t],(function(t){t.call(n,e,r,Jt)}))},de=function(t){var e=void 0;if(fe("beforeSanitizeElements",t,null),function(t){return t instanceof W&&("string"!=typeof t.nodeName||"string"!=typeof t.textContent||"function"!=typeof t.removeChild||!(t.attributes instanceof w)||"function"!=typeof t.removeAttribute||"function"!=typeof t.setAttribute||"string"!=typeof t.namespaceURI||"function"!=typeof t.insertBefore)}(t))return se(t),!0;if(g(t.nodeName,/[\u0080-\uFFFF]/))return se(t),!0;var r=Qt(t.nodeName);if(fe("uponSanitizeElement",t,{tagName:r,allowedTags:vt}),!he(t.firstElementChild)&&(!he(t.content)||!he(t.content.firstElementChild))&&_(/<[/\w]/g,t.innerHTML)&&_(/<[/\w]/g,t.textContent))return se(t),!0;if("select"===r&&_(/